上一篇文章已經讨論了linux套接字基于TCP的用戶端和伺服器端程式設計,這片文章詳細讨論linux套接字基于UDP的用戶端和伺服器端程式設計。
UDP用戶端伺服器模式
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiQ3chVEa0V3bT9CX5RXa2Fmcn9CXwczLcVmds92czlGZvwVP9EUTDZ0aRJkSwk0LcxGbpZ2LcBDM08CXlpXazRnbvZ2LcRlMMVDT2EWNvwFdu9mZvwldGdVY0kzVklHbXl1bk1mYohmMjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zN4MzMwYTM2EDMxQDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
UDP與TCP相比要簡潔很多,UDP不需要listen,accept和connect過程。
1. socket函數建立套接字
#include <sys/types.h>
#include <sys/socket.h>
sockfd = socket(AF_INET, SOCK_DGRAM, );
UDP是資料報的形式,是以在建立套接字時,是SOCK_DGRAM,這是與TCP不同的地方。
2. bind函數,綁定伺服器位址到套接字上
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函數與TCP的使用相同,将伺服器的知名端口号和IP位址綁定到伺服器套接字位址上,IP位址可能有多個。
3. sendto函數,發送資料給指定位址
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
sendto函數比send函數多出兩個參數,一個是目的位址,一個是位址長度。告訴用戶端發送給哪個IP位址和哪個端口号。
3. recvfrom函數,接收資料
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
recvfrom函數比recv函數多出兩個參數,相當于TCP的accept函數,告訴我們是誰發送了資料過來。
服務端程式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#define BUFLEN 100
const char* IP = "127.0.0.1";
const unsigned int SERV_PORT = ;
void Chat(int sockfd);
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in s_addr, c_addr;
char buf[BUFLEN];
socklen_t len;
/*建立socket*/
if((sockfd = socket(AF_INET, SOCK_DGRAM, )) == -){
perror("socket");
exit(errno);
}
/*設定伺服器ip*/
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(SERV_PORT);
s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*把位址和端口幫定到套接字上*/
if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -){
perror("bind");
exit(errno);
}
printf("*****************server start***************\n");
len = sizeof(struct sockaddr);
while()
{
int n = ;
/******接收消息*******/
bzero(buf,BUFLEN);
n = recvfrom(sockfd, buf, BUFLEN, , (struct sockaddr*)&c_addr, &len);
if(n > )
printf("receive massage:%s\n",buf);
else
{
if(n < )
printf("receive failed\n");
else//伺服器調用close函數後,系統阻塞函數調用,傳回0
printf("client stop\n");
break;
}
_retry:
/******發送消息*******/
bzero(buf,BUFLEN);
printf("enter your words:");
/*fgets函數:從流中讀取BUFLEN-1個字元*/
fgets(buf,BUFLEN,stdin);
if(!strncasecmp(buf,"quit",))
{
printf("server stop\n");
break;
}
/*如果輸入的字元串隻有"\n",即回車,那麼請重新輸入*/
if(!strncmp(buf,"\n",))
{
goto _retry;
}
/*如果buf中含有'\n',那麼要用strlen(buf)-1,去掉'\n'*/
if(strchr(buf,'\n'))
{
n = sendto(sockfd, buf, strlen(buf)-, , (struct sockaddr*)&c_addr, len);
}
/*如果buf中沒有'\n',則用buf的真正長度strlen(buf)*/
else
{
n = sendto(sockfd,buf,strlen(buf),, (struct sockaddr*)&c_addr, len);
}
if(n > )
printf("send successful\n");
else{
printf("send failed\n");
break;
}
}
/*關閉已連接配接套接字*/
close(sockfd);
return ;
}
用戶端程式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#define BUFLEN 100
const char* IP = "127.0.0.1";
const int SERV_PORT = ;
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in send_addr; //伺服器套接字位址
struct sockaddr_in recv_addr;
socklen_t send_len, recv_len;
char send_buf[BUFLEN], recv_buf[BUFLEN];
/*建立socket*/
if((sockfd = socket(AF_INET, SOCK_DGRAM, )) == -){
perror("socket");
exit(errno);
}
/*設定伺服器ip*/
bzero(&send_addr, sizeof(send_addr));
send_addr.sin_family = AF_INET;
send_addr.sin_port = htons(SERV_PORT);
if(inet_aton(IP, (struct in_addr*)&send_addr.sin_addr) == )
{
perror("IP error");
exit(errno);
}
printf("*****************client start***************\n");
send_len = sizeof(struct sockaddr);
while()
{
int n = ;
_retry:
/******發送消息*******/
bzero(send_buf,BUFLEN);
printf("enter your words:");
/*fgets函數:從流中讀取BUFLEN-1個字元*/
fgets(send_buf,BUFLEN,stdin);
if(!strncasecmp(send_buf,"quit",))
{
printf("client stop\n");
break;
}
/*如果輸入的字元串隻有"\n",即回車,那麼請重新輸入*/
if(!strncmp(send_buf,"\n",))
{
goto _retry;
}
/*如果buf中含有'\n',那麼要用strlen(buf)-1,去掉'\n'*/
if(strchr(send_buf,'\n'))
{
n = sendto(sockfd,send_buf,strlen(send_buf)-,, (struct sockaddr*)&send_addr, send_len);
}
/*如果buf中沒有'\n',則用buf的真正長度strlen(buf)*/
else
{
n = sendto(sockfd,send_buf,strlen(send_buf),, (struct sockaddr*)&send_addr, send_len);
}
if(n == -)
{
printf("send failed\n");
break;
}
else
{
printf("send successful\n");
}
/******接收消息*******/
bzero(recv_buf,BUFLEN);
n = recvfrom(sockfd,recv_buf,BUFLEN,, (struct sockaddr*)&recv_addr, &recv_len);
if(n > )
{
printf("receive massage:%s\n",recv_buf);
}
else
{
if(n < )
printf("receive failed\n");
else
printf("server stop\n");
break;
}
}
/*關閉連接配接*/
close(sockfd);
return ;
}
但是,在這個程式中,是以recvfrom函數可以接收來自任何位址的資料,如果該某個資料包本不是發送給該位址的,但是由于傳輸的過程中目的IP的端口,被錯誤的改成該位址,那麼這些資料就不應該被接收,是以需要進行判斷接收包的源位址是否是發送資料的目的位址。
//判斷接受位址是否為發送位址
if(send_len != recv_len || memcmp(&send_addr, &recv_addr, send_len) != )
{
printf("recive from %s (ignored)\n", inet_ntoa(recv_addr.sin_addr));
continue;
}
但是采用上述方法也是有問題的,如果客戶機是多宿,發送時核心選擇IP位址就可能是随機的。
UDP的connect函數
UDP的套接字分為已連接配接的套接字和未連接配接的套接字,預設的是未連接配接的套接字,上面的例程采用的是未連接配接的套接字。
UDP的connect函數形式與TCP的相同,但是作用的實作也是不同的。TCP的connect會完成三次握手,而UDP的connect不會,UDP的connect隻是告訴核心儲存了對端的IP和端口号,核心以後就将該套接字的資料發給這個對端位址,從這個對端位址收到的資料也會發送給客戶程式。
在伺服器未啟動的情況下,啟動用戶端并發送消息。對于未連接配接的套接字而言,用戶端不能收到伺服器主機傳回的ICMP差錯封包。而已連接配接的套接字可以收到。
UDP已連接配接的套接字隻能實作一對一的傳輸,如果要從多個地方接受資料和發送資料,則隻能使用未連接配接的套接字。是以,UDP用戶端多用已連接配接的套接字,服務端用未連接配接的套接字。
UDP缺乏流量控制
UDP沒有TCP那樣的視窗通知過程,是以,如果UDP的伺服器收到的資料過快,主機來不及處理,就可能導緻套接字緩沖區被占滿。套接字的緩沖區被填滿後,新到達的資料報就會被丢棄,導緻大量丢包。