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_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 80int 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 80int 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 socketint 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