Linux 下socket通信終極指南(附TCP、UDP完整代碼)


linux下用socket通信,有TCP、UDP兩種協議,網上的很多教程把兩個混在了一起,或者只講其中一種。現在我把自己這兩天研究的成果匯總下來,寫了一個完整的,適合初學者參考,也方便自己以后查閱。

首先講什么是socket,不喜歡理論的可以略過。

Berkeley套接字應用程序接口(API)包括了一個用C語言寫成的應用程序開發庫,主要用於實現進程間通訊,在計算機網絡通訊方面被廣泛使用。(來自 wikipedia socket )

下面介紹一下常用的socket API(也來自 wikipedia socket

 

這個列表是一個Berkeley套接字API庫提供的函數或者方法的概要:

  • socket() 創建一個新的確定類型的套接字,類型用一個整型數值標識,並為它分配系統資源。
  • bind() 一般用於服務器端,將一個套接字與一個套接字地址結構相關聯,比如,一個指定的本地端口和IP地址。
  • listen() 用於服務器端,使一個綁定的TCP套接字進入監聽狀態。
  • connect() 用於客戶端,為一個套接字分配一個自由的本地端口號。 如果是TCP套接字的話,它會試圖獲得一個新的TCP連接。
  • accept() 用於服務器端。 它接受一個從遠端客戶端發出的創建一個新的TCP連接的接入請求,創建一個新的套接字,與該連接相應的套接字地址相關聯。
  • send()recv(),或者write()read(),或者recvfrom()sendto(), 用於往/從遠程套接字發送和接受數據。
  • close() 用於系統釋放分配給一個套接字的資源。 如果是TCP,連接會被中斷。
  • gethostbyname()gethostbyaddr() 用於解析主機名和地址。
  • select() 用於修整有如下情況的套接字列表: 准備讀,准備寫或者是有錯誤。
  • poll() 用於檢查套接字的狀態。 套接字可以被測試,看是否可以寫入、讀取或是有錯誤。
  • getsockopt() 用於查詢指定的套接字一個特定的套接字選項的當前值。
  • setsockopt() 用於為指定的套接字設定一個特定的套接字選項。

更多的細節如下給出。

[編輯]socket()

socket() 為通訊創建一個端點,為套接字返回一個文件描述符。 socket() 有三個參數:

  • domain 為創建的套接字指定協議集。 例如:
    • PF_INET 表示IPv4網絡協議
    • PF_INET6 表示IPv6
    • PF_UNIX 表示本地套接字(使用一個文件)
  • type 如下:
    • SOCK_STREAM (可靠的面向流服務或流套接字)
    • SOCK_DGRAM (數據報文服務或者數據報文套接字)
    • SOCK_SEQPACKET (可靠的連續數據包服務)
    • SOCK_RAW (在網絡層之上的原始協議)。
  • protocol 指定實際使用的傳輸協議。 最常見的就是IPPROTO_TCPIPPROTO_SCTPIPPROTO_UDPIPPROTO_DCCP。這些協議都在<netinet/in.h>中有詳細說明。 如果該項為“0”的話,即根據選定的domain和type選擇使用缺省協議。

如果發生錯誤,函數返回值為-1。 否則,函數會返回一個代表新分配的描述符的整數。

原型:
int socket(int domain, int type, int protocol)

[編輯]bind()

bind() 為一個套接字分配地址。當使用socket()創建套接字后,只賦予其所使用的協議,並未分配地址。在接受其它主機的連接前,必須先調用bind()為套接字分配一個地址。bind()有三個參數:

  • sockfd, 表示使用bind函數的套接字描述符
  • my_addr, 指向sockaddr結構(用於表示所分配地址)的指針
  • addrlen, 用socklen_t字段指定了sockaddr結構的長度

如果發生錯誤,函數返回值為-1,否則為0。

原型
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

[編輯]listen()

當socket和一個地址綁定之后,listen()函數會開始監聽可能的連接請求。然而,這只能在有可靠數據流保證的時候使用,例如:數據類型(SOCK_STREAM,SOCK_SEQPACKET)。

listen()函數需要兩個參數:

  • sockfd, 一個socket的描述符.
  • backlog, 一個決定監聽隊列大小的整數,當有一個連接請求到來,就會進入此監聽隊列,當隊列滿后,新的連接請求會返回錯誤。當請求被接受,返回 0。反之,錯誤返回 -1。

原型:

int listen(int sockfd, int backlog);

[編輯]accept()

當應用程序監聽來自其他主機的面對數據流的連接時,通過事件(比如Unix select()系統調用)通知它。必須用 accept()函數初始化連接。 Accept() 為每個連接創立新的套接字並從監聽隊列中移除這個連接。它使用如下參數:

  • sockfd,監聽的套接字描述符
  • cliaddr, 指向sockaddr 結構體的指針,客戶機地址信息。
  • addrlen,指向 socklen_t的指針,確定客戶機地址結構體的大小 。

返回新的套接字描述符,出錯返回-1。進一步的通信必須通過這個套接字。

Datagram 套接字不要求用accept()處理,因為接收方可能用監聽套接字立即處理這個請求。

函數原型:
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

[編輯]connect()

connect()系統調用為一個套接字設置連接,參數有文件描述符和主機地址。

某些類型的套接字是無連接的,大多數是UDP協議。對於這些套接字,連接時這樣的:默認發送和接收數據的主機由給定的地址確定,可以使用 send()和 recv()。 返回-1表示出錯,0表示成功。

函數原型:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

 

TCP socket通信

服務器端流程如下:

1.創建serverSocket

2.初始化 serverAddr(服務器地址)

3.將socket和serverAddr 綁定 bind

4.開始監聽 listen

5.進入while循環,不斷的accept接入的客戶端socket,進行讀寫操作write和read

6.關閉serverSocket

客戶端流程:

1.創建clientSocket

2.初始化 serverAddr

3.鏈接到服務器 connect

4.利用write和read 進行讀寫操作

5.關閉clientSocket

具體實現代碼如下

 

#server.c(TCP)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#define SRVPORT 10005
#define CONNECT_NUM 5
#define MAX_NUM 80
int main()
{
    int serverSock=-1,clientSock=-1;
    struct sockaddr_in serverAddr;

    serverSock=socket(AF_INET,SOCK_STREAM,0);
    if(serverSock<0)
    {
        printf("socket creation failed\n");
        exit(-1);
    }
    printf("socket create successfully.\n");

    memset(&serverAddr,0,sizeof(serverAddr));
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_port=htons((u_short) SRVPORT);
    serverAddr.sin_addr.s_addr=htons(INADDR_ANY);
    if(bind(serverSock,&serverAddr,sizeof(struct sockaddr_in))==-1)
    {
        printf("Bind error.\n");
        exit(-1);
    }
    printf("Bind successful.\n");

    if(listen(serverSock,10)==-1)
    {
        printf("Listen error!\n");
    }
    printf("Start to listen!\n");

    char revBuf[MAX_NUM]={0};
    char sedBuf[MAX_NUM]={0};
    while(1)
    {
        clientSock=accept(serverSock,NULL,NULL);
        while(1)
        {
            if(read(clientSock,revBuf,MAX_NUM)==-1)
            {
                printf("read error.\n");
            }
            else
            {
                printf("Client:%s\n",revBuf);
            }
            if(strcmp(revBuf,"Quit")==0||strcmp(revBuf,"quit")==0)
            {
                strcpy(sedBuf,"Goodbye,my dear client!");
            }
            else
            {
                strcpy(sedBuf,"Hello Client.");
            }
            if(write(clientSock,sedBuf,sizeof(sedBuf))==-1)
            {
                printf("Send error!\n");
            }
            printf("Me(Server):%s\n",sedBuf);
            if(strcmp(revBuf,"Quit")==0||strcmp(revBuf,"quit")==0)
            {
                break;
            }
            bzero(revBuf,sizeof(revBuf));
            bzero(sedBuf,sizeof(sedBuf));
        }
        close(clientSock);
    }
    close(serverSock);
    return 0;
}
#client.c(TCP)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#define SRVPORT 10005
#define CONNECT_NUM 5
#define MAX_NUM 80
int main()
{
    int clientSock=-1;
    struct sockaddr_in serverAddr;

    clientSock=socket(AF_INET,SOCK_STREAM,0);
    if(clientSock<0)
    {
        printf("socket creation failed\n");
        exit(-1);
    }
    printf("socket create successfully.\n");

    memset(&serverAddr,0,sizeof(serverAddr));
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_port=htons((u_short) SRVPORT);
    serverAddr.sin_addr.s_addr=htons(INADDR_ANY);
    if(connect(clientSock,&serverAddr,sizeof(struct sockaddr_in))<0)
    {
        printf("Connect error.\n");
        exit(-1);
    }
    printf("Connect successful.\n");

    char sedBuf[MAX_NUM]={0};
    char revBuf[MAX_NUM]={0};
    while(gets(sedBuf)!=-1)
    {
        if(write(clientSock,sedBuf,strlen(sedBuf))==-1)
        {
            printf("send error!\n");
        }
        printf("Me(Client):%s\n",sedBuf);
        bzero(sedBuf,sizeof(sedBuf));
        if(read(clientSock,revBuf,MAX_NUM)==-1)
        {
            printf("rev error!\n");
        }
        printf("Sever:%s\n",revBuf);
        if(strcmp(revBuf,"Goodbye,my dear client!")==0)
            break;
        bzero(revBuf,sizeof(revBuf));
    }
    close(clientSock);
    return 0;
}


UDP協議不能保證數據通信的可靠性,但是開銷更低,編起來也更加簡單

 

服務器流程:

1.創建serverSocket

2.設置服務器地址 serverAddr

3.將serverSocket和serverAddr綁定 bind

4.開始進行讀寫 sendto和recvfrom

5.關閉serverSocket

客戶端流程

1.創建clientSocket

2.設置服務器地址 serverAddr

3.可選 設置clientAddr並和clientSocket(一般不用綁定)

4.進行發送操作 sendto

5.關閉clientSocket

具體代碼如下:

 

#server.c(UDP)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>//for sockaddr_in
#include <arpa/inet.h>//for socket
int main()
{
 int fd=socket(AF_INET,SOCK_DGRAM,0);
 if(fd==-1)
 {
 perror("socket create error!\n");
 exit(-1);
 }
 printf("socket fd=%d\n",fd);


 struct sockaddr_in addr;
 addr.sin_family=AF_INET;
 addr.sin_port=htons(6666);
 addr.sin_addr.s_addr=inet_addr("127.0.0.1");


 int r;
 r=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
 if(r==-1)
 {
 printf("Bind error!\n");
 close(fd);
 exit(-1);
 }
 printf("Bind successfully.\n");
 
 char buf[255];
 struct sockaddr_in from;
 socklen_t len;
 len=sizeof(from);
 while(1)
 {
 r=recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&from,&len);
 if(r>0)
 {
 buf[r]=0;
 printf("The message received for %s is :%s\n",inet_ntoa(from.sin_addr),buf);
 }
 else
 {
 break;
 }
 }
 close(fd);
 return 0;
}

 

#client.c(UDP)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>//for sockaddr_in
#include <arpa/inet.h>//for socket 
int main()
{
 int fd=socket(AF_INET,SOCK_DGRAM,0);
 if(fd==-1)
 {
 perror("socket create error!\n");
 exit(-1);
 }
 printf("socket fd=%d\n",fd);


 struct sockaddr_in addr_to;//目標服務器地址
 addr_to.sin_family=AF_INET;
 addr_to.sin_port=htons(6666);
 addr_to.sin_addr.s_addr=inet_addr("127.0.0.1");
 
 struct sockaddr_in addr_from;
 addr_from.sin_family=AF_INET;
 addr_from.sin_port=htons(0);//獲得任意空閑端口
 addr_from.sin_addr.s_addr=htons(INADDR_ANY);//獲得本機地址
 r=bind(fd,(struct sockaddr*)&addr_from,sizeof(addr_from));

int r; 
 if(r==-1)
 {
 printf("Bind error!\n");
 close(fd);
 exit(-1);
 }
 printf("Bind successfully.\n");


 char buf[255];
 int len;
 while(1)
 {
 r=read(0,buf,sizeof(buf)); 
 if(r<0)
 {
 break;
 }
 len=sendto(fd,buf,r,0,(struct sockaddr*)&addr_to,sizeof(addr_to)); 
 if(len==-1)
 {
 printf("send falure!\n");
 }
 else
 {
 printf("%d bytes have been sended successfully!\n",len);
 }
 }
 close(fd);
 return 0;
}


以上代碼均經過測試(Ubuntu12.04),可以運行。有疑問,可以發電郵到ladd.cn@gmail.com

 

參考文章

1.wikipedia socket http://zh.wikipedia.org/wiki/Socket

2.TCP socket 之linux實現http://os.51cto.com/art/201001/179878.htm

 本文由ladd原創,歡迎轉載,但請注明出處:

http://www.cnblogs.com/ladd/archive/2012/06/25/2560888.html 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM