首页 > 代码库 > linux系统socket通信编程实践

linux系统socket通信编程实践

简单介绍并实现了基于UDP(TCP)的windows(UNIX下流程基本一致)下的服务端和客户端的程序,本文继续探讨关于UDP编程的一些细节。
下图是一个简单的UDP客户/服务器模型:
技术分享

<style>.imageplus-append-lu-img-txt { overflow: hidden; margin: 10px 0 } .imageplus-append-nova-txt { border: 1px solid #f2f2f2; font-family: Microsoft YaHei; line-height: normal } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item { position: relative; width: 100%; height: 50px; background-color: #fff } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item a { text-decoration: none } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item a:hover { text-decoration: underline } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item div { white-space: nowrap; overflow: hidden; width: auto; height: 25px; line-height: 25px; margin: 0 16px; font-weight: normal } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title span { font-size: 14px; font-weight: bold; color: #003397 } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc span { font-size: 12px; color: #333 } .imageplus-append-jian { width: 20px; height: 20px; background-image: url("http://ecmb.bdimg.com/public03/imageplus_m_append_jian_151204.png"); background-repeat: no-repeat; background-position: 0 0; position: absolute; top: 0; left: 0 } .imageplus-append-close-btn { width: 40px; height: 40px; position: absolute; right: 0; top: 0; background-image: url("http://ecmb.bdimg.com/public03/imageplus_m_append_close_btn_151113.png"); background-repeat: no-repeat; background-position: 0 0; display: none } .imageplus-append-logo { height: 18px; width: 18px; background: url("http://cpro.baidustatic.com/cpro/ui/noexpire/img/2.0.1/bg.png") no-repeat left top; position: absolute; right: 0; bottom: 0 } .imageplus-append-nova-txt-ue2 { font-family: Microsoft YaHei; float: left; border: 1px solid #ddd; border-top: 3px solid #ff2f62; background-color: #f9f9f9 } .imageplus-append-nova-txt-ue2 a:focus { outline: 0 } .imageplus-append-nova-txt-ue2 .imageplus-append-content { float: left } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item { margin-left: 44px; height: 60px; padding-top: 5px; padding-bottom: 5px } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item a { text-decoration: none } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item div { white-space: nowrap; overflow: hidden } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title { height: 30px; line-height: 30px; font-size: 16px } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title a { color: #000 } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-true { float: left } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-click { float: left; width: 96px } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-click a { color: #ff2f62 } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc { height: 26px; line-height: 26px; font-size: 12px } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc a { color: #7b7b7b } .imageplus-append-nova-txt-ue2 .imageplus-append-go-btn { float: right; margin-top: 19px; margin-right: 18px } .imageplus-append-nova-txt-ue2 .imageplus-append-go-btn a { text-decoration: none } .imageplus-append-nova-txt-ue2 .imageplus-append-go-btn div { width: 100px; height: 32px; line-height: 32px; text-align: center; background-color: #ff2f62; border: 0; color: #fff; font-family: Microsoft YaHei; font-size: 16px; cursor: pointer } .imageplus-append-nova-txt-ue2 .imageplus-append-jian { position: absolute; top: 3px; left: 10px; width: 22px; height: 40px; background-image: url("http://ecma.bdimg.com/public03/imageplus/append/nova_txt_star_160426.png"); background-position: 0 0; background-repeat: no-repeat } .imageplus-append-nova-txt-ue2 .imageplus-append-close-btn { display: none } .imageplus-append-nova-txt-ue2 .imageplus-append-logo { display: none } .imageplus-append-nova-txt-ue2 .imageplus-baidu-logo { position: absolute; bottom: 0; right: 0; z-index: 9999; background: url("http://ecma.bdimg.com/public03/imageplus/logo.png") no-repeat; background-position: 0 -17px; width: 16px; height: 16px } .imageplus-append-nova-txt-ue2 .imageplus-ad-logo { position: absolute; left: 0; bottom: 0; overflow: hidden; z-index: 12; background: url("http://ecma.bdimg.com/public03/imageplus/logo.png") no-repeat; background-position: 0 0; width: 34px; height: 16px } .imageplus-append { float: none; margin: 0; padding: 0; border: 0; overflow: hidden; visibility: visible; text-align: left; background: transparent; position: relative; text-indent: 0; display: inline-block } .imageplus-append div { float: none; margin: 0; padding: 0; border: 0; overflow: hidden; position: static; display: block; visibility: visible; text-align: left; background: transparent; font-family: Microsoft YaHei; line-height: normal } .imageplus-append a,.imageplus-append img,.imageplus-append span { float: none; margin: 0; padding: 0; border: 0; overflow: visible; position: static; display: inline; visibility: visible; text-align: left; background: transparent; font-family: Microsoft YaHei; line-height: normal }</style>



我在这里也实现了一个简单的UDP回射服务器/客户端:
技术分享


?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**实践: 实现一个基于UDP的echo回声server/client**/ 
//server端代码 
void echoServer(int sockfd); 
int main() 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0); 
    if (sockfd == -1
        err_exit("socket error"); 
   
    struct sockaddr_in servAddr; 
    servAddr.sin_family = AF_INET; 
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    servAddr.sin_port = htons(8001); 
    if (bind(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1
        err_exit("bind error"); 
   
    echoServer(sockfd); 
void echoServer(int sockfd) 
    char buf[BUFSIZ]; 
    ssize_t recvBytes = 0
    struct sockaddr_in clientAddr; 
    socklen_t addrLen; 
    while (true
    
        memset(buf, 0, sizeof(buf)); 
        addrLen = sizeof(clientAddr); 
        memset(&clientAddr, 0, addrLen); 
        recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0
                             (struct sockaddr *)&clientAddr, &addrLen); 
        //如果recvBytes=0, 并不代表对端连接关闭, 因为UDP是无连接的 
        if (recvBytes < 0
        
            if (errno == EINTR) 
                continue
            else 
                err_exit("recvfrom error"); 
        
   
        cout << buf ; 
        if (sendto(sockfd, buf, recvBytes, 0
                   (const struct sockaddr *)&clientAddr, addrLen) == -1
            err_exit("sendto error"); 
    
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**client端代码**/ 
void echoClient(int sockfd); 
int main() 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0); 
    if (sockfd == -1
        err_exit("socket error"); 
    echoClient(sockfd); 
    cout << "Client exiting..." << endl; 
void echoClient(int sockfd) 
    struct sockaddr_in servAddr; 
    servAddr.sin_family = AF_INET; 
    servAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
    servAddr.sin_port = htons(8001); 
    char buf[BUFSIZ] = {0}; 
    while (fgets(buf, sizeof(buf), stdin) != NULL) 
    
        if (sendto(sockfd, buf, strlen(buf), 0
                   (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1
            err_exit("sendto error"); 
        memset(buf, 0, sizeof(buf)); 
        int recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL); 
        if (recvBytes == -1
        
            if (errno == EINTR) 
                continue
            else 
                err_exit("recvfrom error"); 
        
        cout << buf ; 
        memset(buf, 0, sizeof(buf)); 
    
}

UDP协议并不是像TCP一样是一对一的通信,UDP可以实现广播通信,并且由于是无连接的,只要知道对等方地址(ip和port) 都可以主动发数据。关闭server,再连接上,还可以进行通信。


UDP编程的注意事项和细节:
1.UDP不存在粘包问题,因为不是基于流的传输(基于消息)。
2.UDP报文可能会丢失、重复、乱序问题。处理丢失可以采用超时处理机制,计时器超时重传;处理重复、乱序可以靠维护数据报之间的序号解决。
3.UDP缺乏流量控制:当缓冲区写满以后,由于UDP没有流量控制机制,因此会覆盖缓冲区。可以通过模拟TCP的滑动窗口协议解决。
4.数据报截断:如果对端发送的UDP数据报大于本地接收缓冲区,报文可能被截断,后面的部分会丢失(而不是像我们想象的下一次能够接收到);并且如果我们使用sendto发送"ABCD"4个字节,接受的recv函数采用循环的方式每次接收一个字符,然而结果却是我们只能接受到A。剩下的不会存在于缓冲区,这也可以形成判断依据: 会丢失报式套接口(UDP)不是流式套接口(TCP)。
5.recvfrom返回0:不代表连接关闭,因为UDP是无连接的。
6.ICMP异步错误(重点)
服务器或者客户端只有一方打开的时候,会发生这个错误,并且这是TCP/IP协议栈产生的一个ICMP应答错误,recv的时候才能收到。因为根本得不到通知,所以称作异步,不会返回给未连接的套接字。
解决方法:UDP调用connect
当增加上connect后,send之后,如果对方处于未开启的话,那么recv会接受到ICMP错误。UDP的connect没有三次握手,仅仅是维护了一个信息,一个状态,并且为这个套接字不能发送给其他地址。connect后不指定sendto的目的地址也可以,因为连接的时候已经指定了,并且还可以用send,write的方法。
进一步说明:


1)UDP发送报文的时,只把数据copy到发送缓冲区。在服务器没有起来的情况下,可以发送成功。
2)所谓ICMP异步错误是指:发送的报文的时候,没有错误,接受报文recvfrom的时候,回收到ICMP应答.
3)异步的错误,无法返回未连接的套接字, 因此如果上例我们调用了connect, 是可以收到该异步ICMP报文的;


对1)的进一步分析:
如果服务器没启动,客户端sendto发送一个数据,结果会是什么呢?
结论是,sendto成功返回,如果客户端还同时调用了recvfrom,则将永远堵塞在recvfrom函数(当然可以设超时),同时通过tcpdump还可以看到,服务端返回ICMP port unreachable错误消息,但是这个消息并没有通过sendto和recvfrom函数返回给用户进程,换句话说,用户并不知道服务端返回了ICMP错误。怎么办呢,udp的connet函数可以搞定这些。
对于udp socket调用connect,称之为已连接UDP socket,其与未连接UDP socket区别如下:
不使用sendto,而使用write或send,因为在connect中,已经指定目的端IP地址;不应用recvfrom,而使用read或recv或recvmsg,注意,如果源地址不是connect连接的目的地址,是不会回馈到该套接字的,这正好由内核帮我们完成了验证接收到的响应;由已连接的UDP套接字引发的异步错误,会返回给他们所在的进程,这样就很好的解决了上述问题;在调用connect,确定目的IP和port之外,同时还会通过目的地址,查找路由表,确定本地地址,connect之后调用getsockname可以获取到。 技术分享

<style>.imageplus-append-lu-img-txt { overflow: hidden; margin: 10px 0 } .imageplus-append-nova-txt { border: 1px solid #f2f2f2; font-family: Microsoft YaHei; line-height: normal } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item { position: relative; width: 100%; height: 50px; background-color: #fff } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item a { text-decoration: none } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item a:hover { text-decoration: underline } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item div { white-space: nowrap; overflow: hidden; width: auto; height: 25px; line-height: 25px; margin: 0 16px; font-weight: normal } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title span { font-size: 14px; font-weight: bold; color: #003397 } .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc span { font-size: 12px; color: #333 } .imageplus-append-jian { width: 20px; height: 20px; background-image: url("http://ecmb.bdimg.com/public03/imageplus_m_append_jian_151204.png"); background-repeat: no-repeat; background-position: 0 0; position: absolute; top: 0; left: 0 } .imageplus-append-close-btn { width: 40px; height: 40px; position: absolute; right: 0; top: 0; background-image: url("http://ecmb.bdimg.com/public03/imageplus_m_append_close_btn_151113.png"); background-repeat: no-repeat; background-position: 0 0; display: none } .imageplus-append-logo { height: 18px; width: 18px; background: url("http://cpro.baidustatic.com/cpro/ui/noexpire/img/2.0.1/bg.png") no-repeat left top; position: absolute; right: 0; bottom: 0 } .imageplus-append-nova-txt-ue2 { font-family: Microsoft YaHei; float: left; border: 1px solid #ddd; border-top: 3px solid #ff2f62; background-color: #f9f9f9 } .imageplus-append-nova-txt-ue2 a:focus { outline: 0 } .imageplus-append-nova-txt-ue2 .imageplus-append-content { float: left } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item { margin-left: 44px; height: 60px; padding-top: 5px; padding-bottom: 5px } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item a { text-decoration: none } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item div { white-space: nowrap; overflow: hidden } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title { height: 30px; line-height: 30px; font-size: 16px } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title a { color: #000 } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-true { float: left } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-click { float: left; width: 96px } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-click a { color: #ff2f62 } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc { height: 26px; line-height: 26px; font-size: 12px } .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc a { color: #7b7b7b } .imageplus-append-nova-txt-ue2 .imageplus-append-go-btn { float: right; margin-top: 19px; margin-right: 18px } .imageplus-append-nova-txt-ue2 .imageplus-append-go-btn a { text-decoration: none } .imageplus-append-nova-txt-ue2 .imageplus-append-go-btn div { width: 100px; height: 32px; line-height: 32px; text-align: center; background-color: #ff2f62; border: 0; color: #fff; font-family: Microsoft YaHei; font-size: 16px; cursor: pointer } .imageplus-append-nova-txt-ue2 .imageplus-append-jian { position: absolute; top: 3px; left: 10px; width: 22px; height: 40px; background-image: url("http://ecma.bdimg.com/public03/imageplus/append/nova_txt_star_160426.png"); background-position: 0 0; background-repeat: no-repeat } .imageplus-append-nova-txt-ue2 .imageplus-append-close-btn { display: none } .imageplus-append-nova-txt-ue2 .imageplus-append-logo { display: none } .imageplus-append-nova-txt-ue2 .imageplus-baidu-logo { position: absolute; bottom: 0; right: 0; z-index: 9999; background: url("http://ecma.bdimg.com/public03/imageplus/logo.png") no-repeat; background-position: 0 -17px; width: 16px; height: 16px } .imageplus-append-nova-txt-ue2 .imageplus-ad-logo { position: absolute; left: 0; bottom: 0; overflow: hidden; z-index: 12; background: url("http://ecma.bdimg.com/public03/imageplus/logo.png") no-repeat; background-position: 0 0; width: 34px; height: 16px } .imageplus-append { float: none; margin: 0; padding: 0; border: 0; overflow: hidden; visibility: visible; text-align: left; background: transparent; position: relative; text-indent: 0; display: inline-block } .imageplus-append div { float: none; margin: 0; padding: 0; border: 0; overflow: hidden; position: static; display: block; visibility: visible; text-align: left; background: transparent; font-family: Microsoft YaHei; line-height: normal } .imageplus-append a,.imageplus-append img,.imageplus-append span { float: none; margin: 0; padding: 0; border: 0; overflow: visible; position: static; display: inline; visibility: visible; text-align: left; background: transparent; font-family: Microsoft YaHei; line-height: normal }</style>



总结:UDP客户或服务进程,仅在使用自己的UDP套接字与确定的唯一对端进行通信时,才会调用connect,当然,一般UDP客户端会用connect多一点。同时如果采用connect,其性能也会得到提升,因为对于sendto来讲,其发送数据前和后,需要连接套接字、断开套接字,如果connect之后,这两步就省下了。

 

7.UDP外出接口的确定:

假设客户端有多个IP地址,由connect /sendto 函数提供的远程地址的参数,系统会选择一个合适的出口,比如Server的IP是192.168.2.10, 而客户端现在的IP有 192.168.1.32 和 192.168.2.75 那么会自动选择192.168.2.75 这个IP出去。(具体算法先略过...)

 

最后附上数据报截断的一个示例:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int main() 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0); 
    if (sockfd == -1
        err_exit("socket error"); 
   
    struct sockaddr_in servAddr; 
    servAddr.sin_family = AF_INET; 
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    servAddr.sin_port = htons(8001); 
    if (bind(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1
        err_exit("bind error"); 
    //给自己发送数据 
    if (sendto(sockfd, "ABCDE", 5, 0
               (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1
        err_exit("sendto error"); 
   
    for (int i = 0; i < 5; ++i) 
    
        char ch; 
        int recvBytes =  recvfrom(sockfd, &ch, 1, MSG_DONTWAIT, NULL, NULL); 
        if (recvBytes == -1
        
            if (errno == EINTR) 
                continue
            else if (errno == EAGAIN) 
                err_exit("recvfrom error"); 
        
        else 
            cout << "char = " << ch << ", recvBytes = " << recvBytes << endl; 
    
}

linux系统socket通信编程实践