socket編程:簡單UDP服務器/客戶端編程

    對於之前的TCP協議而言,他是可靠的字節流傳輸,而在socket編程中,在不需要保證數據傳輸正確安全的情況下。或者由用戶自己完成傳輸確認情況/服務端客戶端自己實現數據傳輸。套接字編程也提供了UDP協議的方法。


基於UDP(不是面向連接)的socket編程,分爲客戶端和服務器端。

客戶端的流程如下:

(1)創建套接字(socket)

(2)和服務器端進行通信(sendto)

(3)關閉套接字

因爲在socket編程中,UDP是針對數據報的數據傳輸,所以socket專門定義了UDP所使用的函數接口。


sendto函數:指向一指定目的地發送數據,sendto()適用於發送未建立連接的UDP數據包 

650) this.width=650;" src="http://s4.51cto.com/wyfs02/M00/80/F0/wKioL1dFOSTir6ODAABuqnevE6g007.png" title="QQ圖片20160525132903.png" alt="wKioL1dFOSTir6ODAABuqnevE6g007.png" />


返回值爲整型,如果成功,則返回發送的字節數,失敗則返回-1,並SOCKET_ERROR。

sockfd: 套接字  也就是我們所建立的sock文件描述符

buf: 待發送數據的緩衝區 在客戶端中定義的棧存儲數據緩衝區

len: 緩衝區長度   確認的發送長度。

flags:調用方式標誌位, 一般爲0, 改變Flags,將會改變Sendto發送的形式:阻塞 可以定義爲不同的發送方式,有各種不同的發送方式。0爲阻塞發送。

addr:(可選)指針,指向目的套接字的地址。調用存儲我們所連接套接字的信息,

addr:lenaddr所指地址的長度。


服務器端的流程如下:

(1)創建套接字(socket)

(2)將套接字綁定到一個本地地址和端口上(bind)

(3)用返回的套接字和客戶端進行通信(recvfrom)

(4)返回,等待另一個客戶請求。

(5)關閉套接字。

recvfrom函數:本函數用於從(已連接)套接口上接收數據,並捕獲數據發送源的地址。

650) this.width=650;" src="http://s1.51cto.com/wyfs02/M00/80/F0/wKioL1dFOpvx54SFAABSTD5ej3Q004.png" title="QQ圖片20160525133515.png" alt="wKioL1dFOpvx54SFAABSTD5ej3Q004.png" />

sockfd:標識一個已連接套接口的描述字。同上

buf:接收數據緩衝區。同上

len:緩衝區長度。同上

flags:調用操作方式。同上

src_addr:(可選)指針,指向裝有源地址的緩衝區。——輸出型參數。同上

addrlen:(可選)指針,指向from緩衝區長度值。——輸入輸出型參數。同上

返回值爲整型,如果成功,則返回接收到的字節數,失敗則返回-1和SOCKET_ERROR。


總結:其實對於UDP協議的服務端和客戶端而言,他主要是滿足UDP協議的數據快速傳輸,服務端和客戶端快速訪問,沒有TCP的3次握手過程,他只需要確認數據發送出去,不需要保證數據的傳輸是否成功,所以對於這樣的UDP服務器端客戶端而言。他是比較簡單的,服務端不需要進入監聽和連接的建立沒有accept(),listen(),客戶端不需要connnet();


實現機制比較簡單,但是有時候爲了保證數據傳輸的有效,我們需要實現一些其他的機制,這個以後再更新。

我們先看一下簡單版本的實現代碼:

//server端
#include<stdio.h>                                                                      
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
void Usage(const char* proc)
{
    printf("%s [ip][port]\n");
}
int main(int argc,char* argv[])
{
    if(argc!=3){
        Usage(argv[0]);
        return 1;
    }
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0){
        perror("socket");
        return 2;
    }
    int _port=atoi(argv[2]);
    char* _ip=argv[1];
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(_port);
    local.sin_addr.s_addr=inet_addr(_ip);
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
        perror("bind");
        exit(1);
    }
    char buf[1024];
    struct sockaddr_in remote;
    socklen_t len=sizeof(remote);
    while(1)
    {
        ssize_t _s=recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&remote,&len);
        if(_s>0)
        {
            buf[_s]='\0';
            printf("client:[ip:%s][port:%d]  %s",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port),buf);
         }
         else if(_s==0){
            printf("client close");
            break;
        }
        else
        {
            break;
        }
 
    }
    close(sock);
    return 0;
}
//client端
#include<stdio.h>                                                                      
#include<stdlib.h>
#include<errno.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
void Usage(const char* proc)
{
    printf("%s[ip][port]",proc);
}
int main(int argc,char* argv[])
{
    if(argc!=3){
        Usage(argv[0]);
        return 1;
    }
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0){
        perror("socket");
        return 2;
    }
    int _port=atoi(argv[2]);
    char* _ip=argv[1];
    struct sockaddr_in client;
    client.sin_family=AF_INET;
    client.sin_port=htons(_port);
    client.sin_addr.s_addr=inet_addr(_ip);
    char buf[1024];
    while(1)
    {
        ssize_t _s=read(0,buf,sizeof(buf)-1);
        if(_s>0){
            buf[_s]='\0';
        }
        _s=sendto(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,sizeof(client));
    }
    return 0;
}


下面我們來講一下關於端口的問題,這涉及到上一個TCP中所遺留下來的問題:

首先我們需要了解一下端口是什麼:

    (Port)相當於一種數據的傳輸通道。用於接受某些數據,然後傳輸給相應的服務,而電腦將這些數據處理後,再將相應的恢復通過開啓的端口傳給對方。一般每一個端口的開放的偶對應了相應的服務,要關閉這些端口只需要將對應的服務關閉就可以了。

對於端口而言,我們可以將它理解爲任何一個在當我們的應用程序:進程需要進行通信的時候,我們進行網絡上的消息傳輸需要識別,但是在同一IP下,我們無法具體識別是哪個進程進行提取信息。所以就出現了端口。

    每一種東西出現是解決某一種或某一類問題而出現的。在通訊過程中IP是固定只有那麼多的,所以爲了解決通訊地址的重複出現了端口的概念。就相當於電話的分機號碼一樣。


TCP和UDP採用16b的端口號來識別應用程序。那麼這些端口號是如何選擇的呢?

TCP和UDP採用16b的端口號來識別應用程序。那麼這些端口號是如何選擇的呢?

服務器一般都是通過知名端口號來識別的。例如,對於TCP/IP實現來說,每個FTP服務器的TCP端口號都是21,每個Telnet服務器的TCP端口號都是23,每個TFTP(普通文件傳輸協議)服務器的UDP端口號都是69。任何TCP/IP實現所提供的服務都用知名的1~1 023之間的端口號。這些知名端口號由Internet號分配機構(Internet Assigned Numbers Authority, IANA)來管理。到1992年爲止,知名端口號介於1~255之間。256~1 023之間的端口號通常都是由UNIX系統佔用,以提供一些特定的UNIX服務,也就是說,提供一些只有UNIX系統纔有的,而其他操作系統可能不提供的服務。現在IANA管理1~1 023之間所有的端口號。

Internet擴展服務與UNIX特定服務之間的一個差別就是telnet和rlogin,它們二者都允許通過計算機網絡登錄到其他主機上。telnet是採用端口號爲23的TCP/IP標準,且幾乎可以在所有操作系統上進行實現。相反,rlogin最開始時只是爲UNIX系統設計的(儘管許多非UNIX系統現在也提供該服務),因此在20世紀80年代初,它的端口號爲513,客戶端通常對它所使用的端口號並不關心,只須保證該端口號在本機上是唯一的即可。客戶端口號又稱做臨時端口號(即存在時間很短暫),這是因爲它通常只是在用戶運行該客戶程序時才存在,而服務器則只要主機開着,其服務就運行。

大多數TCP/IP實現給臨時端口分配1 024~5 000之間的端口號。大於5 000的端口號是爲其他服務器預留的(Internet上並不常用的服務)。大多數Linux系統的文件/etc/services都包含了人們熟知的端口號。

650) this.width=650;" src="http://s2.51cto.com/wyfs02/M01/80/F0/wKioL1dFPpqx-VFXAAA3LKXJOxo723.png" title="QQ圖片20160525135231.png" alt="wKioL1dFPpqx-VFXAAA3LKXJOxo723.png" />        這個就是我們在程序中經常使用的8080端口。

  需要注意的一點是:
        計算機之間依照互聯網
傳輸層TCP/IP協議不同的協議通信,都有不同的對應端口。所以,利用短信(datagram)的UDP,所採用的端口號碼不一定和採用TCP的端口號碼一樣。以下爲兩種通信協議的端口列表鏈接:


然後在上一篇博文中我們留下的一個問題就是server端主動關閉後,如何避免2MSL的TIME_WAIT.

其實就是採取了端口複用的規則,也就是socket的一個服務端的地址:端口能夠重複使用,跟IP複用是同一個道理,都是爲了解決重複相關的問題。

解決上述的問題使server端避免進入2MSL時間在結束之後就能儘快的再次進行全網的監聽呢?可以使用setsockopt函數,設置socket描述符的選項SO_REUSEADDR爲1,其作用是可以允許重用本地端口號和地址:

也就是修改套接字的描述符:

函數原型:

650) this.width=650;" src="http://s5.51cto.com/wyfs02/M00/80/F0/wKioL1dFP-vRfmp9AABPYH_JNAQ720.png" title="QQ圖片20160525135810.png" alt="wKioL1dFP-vRfmp9AABPYH_JNAQ720.png" />

這就是關於套接字選項的設置,以後我會專門寫一篇博客來說明套接字選項。現在我們只需要知道如何避免2MSL

    函數參數中,

    sockfd是創建出的socket文件描述符;

    level被指定爲SOL_SOCKET

    optname是對相應協議模塊的描述,也就是設置sockfd描述符的選項,這裏就設置爲SO_REUSEADDR,允許重用本地地址和端口;

    optvaloptlen用於訪問選項值,也就是optname,這裏的值應設置爲1

    函數成功返回0,失敗返回-1並置相應的錯誤碼;

    在創建監聽socket和綁定函數bind之間插入函數setsockopt。

本文出自 “剩蛋君” 博客,請務必保留此出處http://memory73.blog.51cto.com/10530560/1782996

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章