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原創,歡迎轉載,但請注明出處:
