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
表示IPv6PF_UNIX
表示本地套接字(使用一個文件)
- type 如下:
SOCK_STREAM
(可靠的面向流服務或流套接字)SOCK_DGRAM
(數據報文服務或者數據報文套接字)SOCK_SEQPACKET
(可靠的連續數據包服務)SOCK_RAW
(在網絡層之上的原始協議)。
- protocol 指定實際使用的傳輸協議。 最常見的就是
IPPROTO_TCP
、IPPROTO_SCTP
、IPPROTO_UDP
、IPPROTO_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原創,歡迎轉載,但請注明出處: