筆者今天來講講Linux下Socket通信,包括客戶端和服務端。
客戶端
客戶端連接服務器的思路是:
1.建立Socket鏈路,
2.設置通信方式,服務器的IP和端口,具體設置 sockaddr_in這個結構體參數
3.連接服務器,連接成功就可以收發數據。
4.正常收發數據
5.關閉socket通信
有了思路,就可以上手寫代碼了,哈哈加油!
建立socket鏈接使用 socket(int af, int type, int protocol);這個函數。
參數1:af表示IP類型,AF_INET 表示IPV4類型,如果要使用AF_INET6 則表示IPV6的類型
參數2:type表示套接字類型,SOCK_STREAM,表示面向鏈接的套接字(即TCP),如果是SOCK_DGRAM,則表示面向無連接(數據報)的套接字(即UDP)。
參數3:protocol,表示TCP通信還是UDP通信,一般前面套接字類型就可以決定通信方式,最後一個參數一般設爲0。
設置sockaddr_in結構體,
struct sockaddr_in
{
short sin_family; //地址類型
unsigned short sin_port; //連接端口,需要htons轉換成網絡數據格式
struct in_addr sin_addr; //IP地址,也是網絡地址格式
unsigned char sin_zero[8]; //*Same size as struct sockaddr沒有實際意義,只是爲了 跟SOCKADDR結構在內存中對齊*/
}
struct sockaddr_in ServerAddr;
struct hostent *Host;
Host = gethostbyname(ServerIP);
Sockfd = socket(AF_INET,SOCK_STREAM,0);
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(ServerPort);
ServerAddr.sin_addr = *((struct in_addr *)Host->h_addr); //返回一個網絡數據格式的IP地址。
數據的發送
數據的發送比較簡單,直接調用send函數即可。
int send( SOCKET s, const char FAR buf, int len, int flags );
參數1:socket通信鏈路標示符
參數2:發送緩衝區
參數3:發送的字節數
參數4:一般設置爲0
send(Sockfd,SendBuff,ByteNum,0);
如果返回值爲-1,則說明通信鏈路有問題,需要關閉連接(close(標示符);),重新進行連接。```
數據的接收
數據的接收同樣和上篇博文串口數據接收一樣,需要調用select函數來監測socket標示符是否變化,即是否接收到數據。
相當於觸發接收之後,可以調用 recv函數接收到數據
int len = recv(Sockfd, RecBuff, MaxLength, 0);
參數1:socket標示符
參數2:接收緩存區
參數3:接收最大的字符長度
參數4:一般設置爲0
fd_set rfds_Socket;
FD_ZERO(&rfds_Socket);
FD_SET(Sockfd, &rfds_Socket);
int retval=select(Sockfd + 1, &rfds_Socket, NULL, NULL, NULL);
//tv控制選擇的時間,若規定時間內沒有收到數據
//則不監控該rfds。則若爲NULL,一直等到接收到數據
//再繼續程序執行。
printf("retval=%d--rfds_Socket=%d\n",rfds_Socket);
if(retval==0)
{
printf("select error\n");
continue;
}
while(Sockfd!=-1)
{
if(FD_ISSET(Sockfd, &rfds_Socket)) //接收到數據了
{
bzero(RecBuff, MaxLength);
int len = recv(Sockfd, RecBuff, MaxLength, 0);
strcpy(SendBuff,RecBuff);
ByteNum=len;
memset(RecBuff,0x00,sizeof(RecBuff));
}
}
完整的發送和接收函數代碼如下:調用兩個線程來進行處理。
做法是將服務器收到的數據,再發送回去。
void *pthread_NetRecFun(void *arg)
{
char ServerIP[50];
char Port[50];
while(*(int*)arg)
{
if(Sockfd==-1)
{
ConfigGetKey("NetConfig.dat", "net:server", "ServerIP", ServerIP);
ConfigGetKey("NetConfig.dat", "net:server", "ServerPort", Port);
printf("%s---",ServerIP);
printf("%s\n",Port);
//char *ServerIP = "192.168.0.100";
int ServerPort = atoi(Port);
struct sockaddr_in ServerAddr;
struct hostent *Host;
Host = gethostbyname(ServerIP);
Sockfd = socket(AF_INET,SOCK_STREAM,0);
if(Sockfd == -1)
{
printf("Socket established failed!\n");
sleep(2);
continue;
}
bzero(&ServerAddr,sizeof(ServerAddr));
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(ServerPort);
ServerAddr.sin_addr = *((struct in_addr *)Host->h_addr);
if(connect(Sockfd,(struct sockaddr*)&ServerAddr,sizeof(struct sockaddr))==-1)
{
close(Sockfd);
Sockfd=-1;
printf("Socket connected failed!\n");
sleep(2);
continue;
}
}
printf("connect Successfully*******SocketRec_VST=%d\n",Sockfd);
fd_set rfds_Socket;
FD_ZERO(&rfds_Socket);
FD_SET(Sockfd, &rfds_Socket);
int retval=select(Sockfd + 1, &rfds_Socket, NULL, NULL, NULL);
//tv控制選擇的時間,若規定時間內沒有收到數據
//則不監控該rfds。則若爲NULL,一直等到接收到數據
//再繼續程序執行。
printf("retval=%d--rfds_Socket=%d\n",rfds_Socket);
if(retval==0)
{
printf("select error\n");
continue;
}
while(Sockfd!=-1)
{
if(FD_ISSET(Sockfd, &rfds_Socket)) //接收到數據了
{
bzero(RecBuff, MaxLength);
int len = recv(Sockfd, RecBuff, MaxLength, 0);
strcpy(SendBuff,RecBuff);
ByteNum=len;
memset(RecBuff,0x00,sizeof(RecBuff));
}
}
}
}
void *pthread_NetSendFun(void *arg)
{
char ServerIP[50];
char Port[50];
while(*(int*)arg)
{
if(Sockfd==-1)
{
ConfigGetKey("NetConfig.dat", "net:server", "ServerIP", ServerIP);
ConfigGetKey("NetConfig.dat", "net:server", "ServerPort", Port);
printf("%s---",ServerIP);
printf("%s\n",Port);
//char *ServerIP = "192.168.0.100";
int ServerPort = atoi(Port);
struct sockaddr_in ServerAddr;
struct hostent *Host;
Host = gethostbyname(ServerIP);
Sockfd = socket(AF_INET,SOCK_STREAM,0);
if(Sockfd == -1)
{
printf("Socket established failed!\n");
sleep(2);
continue;
}
bzero(&ServerAddr,sizeof(ServerAddr));
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(ServerPort);
ServerAddr.sin_addr = *((struct in_addr *)Host->h_addr);
if(connect(Sockfd,(struct sockaddr*)&ServerAddr,sizeof(struct sockaddr))==-1)
{
close(Sockfd);
Sockfd=-1;
printf("Socket connected failed!\n");
sleep(2);
continue;
}
}
if(strlen(SendBuff)>0)
{
//在linux下寫socket的程序的時候,如果通信鏈接斷開之後在調用發送函數,就會讓底層拋出一個SIGPIPE信號。這個信號的缺省處理方法是退出進程,但是這都不是我們期望的。因此我們需要重載這個信號的處理方法。調用以下代碼,即可安全的屏蔽SIGPIPE:
signal(SIGPIPE, SIG_IGN);
int len = send(Sockfd,SendBuff,ByteNum,0);
printf("len=%d---",len);
if(len==-1)
{
printf("Failed to Send Message!\n");
close(Sockfd);
Sockfd=-1;
continue;
}
printf("NET:%s--%d---%d\n",SendBuff,ByteNum,strlen(SendBuff));
memset(SendBuff,0x00,sizeof(SendBuff));
ByteNum=0;
}
}
}
網絡助手作爲服務器,本例(Beagle Bone)作爲客戶端,連接成功之後,Beagle Bone將收到網絡助手的數據再轉發回去。
Beagle Bone IP:192.168.0.152
網絡助手服務器IP:192.168.0.100 端口:51230,在同一個局域網段。
服務端
服務端監聽客戶端連接的思路如下:
1.建立socket通信鏈路,確定通信方式(TCP/UDP)和通信地址(IPV4或者IPV6)
2.設置服務器的IP和端口,具體設置 sockaddr_in這個結構體參數
3.將套接字綁定到本地地址和端口上面
4,監聽套接字,保存用戶的請求連接信息。
5.正常收發數據。
6.關閉socket通信
建立socket通信鏈路,設置IP和端口,和客戶端有區別的就是地址設置爲任意即可。其他參數和順序都一樣。
serveraddr.sin_addr.s_addr = INADDR_ANY;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
printf("listenfd=%d\n", listenfd);
//定義一個結構體變量servaddr,用來記錄給定的IP和port信息,爲bind函數做準備
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT); //把端口轉化爲網絡字節序,即大端模式
serveraddr.sin_addr.s_addr = INADDR_ANY;
綁定套接字
參數1;socket通信標示符
參數2:服務器地址信息,包括IP和端口
參數3:數據長度
//把“本地含義的描述符”綁定到一個IP和Port上,此時這個socket才具備對外連接的能力
bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
監聽套接字
參數1;socket通信標示符
參數2:指定了監聽連接的最大個數
//創建一個監聽隊列,用來保存用戶的請求連接信息(ip、port、protocol)
listen(listenfd, BACKLOG);
等待接收客戶端連接,
該函數爲阻塞式,等到有客戶端連接則執行完該函數。
參數1;socket通信標示符
參數2:客戶端地址信息,包括IP和端口
參數3:接收長度
connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peer_len);
連接完成之後,則可以正常和客戶端進行通信。
完整的代碼如下:
int ServerConnectSocket()
{
int listenfd;
//創建一個socket描述符,此描述符僅是本主機上的一個普通文件描述符而已
listenfd = socket(AF_INET, SOCK_STREAM, 0);
printf("listenfd=%d\n", listenfd);
//定義一個結構體變量servaddr,用來記錄給定的IP和port信息,爲bind函數做準備
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT); //把端口轉化爲網絡字節序,即大端模式
serveraddr.sin_addr.s_addr = INADDR_ANY;
//把“本地含義的描述符”綁定到一個IP和Port上,此時這個socket才具備對外連接的能力
bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
//創建一個監聽隊列,用來保存用戶的請求連接信息(ip、port、protocol)
listen(listenfd, BACKLOG);
printf("======bind success,waiting for client's request!!!======\n");
//讓操作系統回填client的連接信息(ip、port、protocol)
return listenfd;
}
int main()
{
TimerFunc();
int connfd;
int listenfd = ServerConnectSocket();
while(1)
{
//accept函數從listen函數維護的監聽隊列裏取一個客戶連接請求處理
struct sockaddr_in peeraddr;
socklen_t peer_len = sizeof(peeraddr);
printf("Port=%d\r\n",PORT);
connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peer_len);
printf("=====================Client Connect Successfully=====================\n");
printf("IP = %s:PORT = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
char buf[MAXDATASIZE];
fd_set rfds_Socket;
FD_ZERO(&rfds_Socket);
FD_SET(connfd, &rfds_Socket);
int retval=select(connfd + 1, &rfds_Socket, NULL, NULL, NULL);
//tv控制選擇的時間,若規定時間內沒有收到數據
//則不監控該rfds。則若爲NULL,一直等到接收到數據
//再繼續程序執行。
printf("retval=%d--rfds_Socket=%d\n",rfds_Socket);
if(retval==0)
{
printf("select error\n");
continue;
}
while(1)
{
if(FD_ISSET(connfd, &rfds_Socket)) //接收到數據了
{
bzero(buf, MAXDATASIZE);
int len = recv(connfd, buf, MAXDATASIZE, 0);
if(len<=0)
{
//close(connfd);
printf("client has closed!\n");
connfd=-1;
break;
}
printf("%s***%d\n",buf,len);
send(connfd, buf, len, 0);
memset(buf, '\0', MAXDATASIZE/sizeof (char));
}
}
}
close(connfd);
close(listenfd);
return 0;
}
網絡助手作爲客戶端,本例(Beagle Bone)作爲服務端,助手連接成功之後,Beagle Bone將收到網絡助手的數據再轉發回去。
助手IP:192.168.0.100 端口:53544
服務器(Beagle Bone)IP:192.168.0.152 端口:51230