linux系统socket通信编程实践
简单介绍并实现了基于UDP(TCP)的windows(UNIX下流程基本一致)下的服务端和客户端的程序,本文继续探讨关于UDP编程的一些细节。
下图是一个简单的UDP客户/服务器模型:
我在这里也实现了一个简单的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可以获取到。
总结: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通信编程实践的更多相关文章
- linux系统socket通信编程详解函数
linux socket编程之TCP与UDP TCP与UDP区别 TCP---传输控制协议,提供的是面向连接.可靠的字节流服务.当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之 ...
- linux系统socket通信编程2
一.概述 TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议. TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的字节流 ...
- linux系统socket通信编程1
Linux下的Socket编程大体上包括Tcp Socket.Udp Socket即Raw Socket这三种,其中TCP和UDP方式的Socket编程用于编写应用层的socket程序,是我们用得比较 ...
- Linux 下socket通信终极指南(附TCP、UDP完整代码)
linux下用socket通信,有TCP.UDP两种协议,网上的很多教程把两个混在了一起,或者只讲其中一种.现在我把自己这两天研究的成果汇总下来,写了一个完整的,适合初学者参考,也方便自己以后查阅. ...
- 【Linux教程】Linux系统零基础编程入门,想当大神?这些你都要学
✍ 文件和文件系统 文件是Linux系统中最重要的抽象,大多数情况下你可以把linux系统中的任何东西都理解为文件,很多的交互操作其实都是通过文件的读写来实现的. 文件描述符 在Linux内核中,文件 ...
- 【转】C# Socket通信编程
https://www.cnblogs.com/dotnet261010/p/6211900.html#undefined 一:什么是SOCKET socket的英文原义是“孔”或“插座”.作为进程通 ...
- linux系统UDP的socket通信编程3
我刚开始接触linux下的socket编程,边抄边理解udp socket编程,我的疑问是server不指定IP地址,client的目标IP地址是127.0.0.1,这样就可以通信吗?在同一主机下是不 ...
- linux系统UDP的socket通信编程2
UDP套接字编程范例: server端代码如下: ? 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 2 ...
- linux系统UDP的socket通信编程
发送方: /* * File: main.c * Author: tianshuai * * Created on 2011年11月29日, 下午10:34 * * 主要实现:发送20个文本消息,然后 ...
随机推荐
- html5标签video(播放器)学习笔记(二)-基本操作
html5标签video(播放器)学习笔记(二)-基本操作 subying 发布时间: 2014/12/01 23:59 阅读: 13008 收藏: 21 点赞: 3 评论: 0 摘要 本文介绍了ht ...
- position定位属性
值 描述 absolute 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位. 元素的位置通过 "left", "top", " ...
- R ggplot2 线性回归
摘自 http://f.dataguru.cn/thread-278300-1-1.html library(ggplot2) x=1:10y=rnorm(10)a=data.frame(x= x, ...
- mysql中使用正则表达式查询
正则表达式功能确实很强大,那天专门抽空学学,这里就暂时在mysql查询中用用. 正则表达式强大而灵活,可以应用于非常复杂的查询. 选项 说明(自动加匹配二字) 例子 匹配值示例 ^ 文本开始字符 '^ ...
- Dropwizard与Spring Boot比较
在这篇文章中我们将讨论的Java轻量级框架Dropwizard和Spring Boot的相似性和差异. 首先,这是一个选择自由和速度需要,无论你在Dropwizard和Spring Boot选择哪个, ...
- WPF路由事件一:逻辑树和可视树
一.什么是逻辑树 逻辑树就是描述WPF界面元素的实际构成,它是由程序在XAML中所有的UI元素组成.最显著的特点就是由布局控件.或者其他常用的控件组成. <Window x:Class=&quo ...
- 【R】提升R代码运算效率的11个实用方法
低.有许多种方法可以提升你的代码运算效率,但或许你更想了解运算效率能得到多大的提升.本文将介绍几种适用于大数据领域的方法,包括简单的逻辑调整设计.并行处理和Rcpp的运用,利用这些方法你可以轻松地处理 ...
- 'cl.exe' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
1.首先找到vcvars32.bat文件,一般在C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin文件夹下 2.打开cmd黑窗 ...
- php 删除目录
<?php /* 自定义的删除函数,可以删除文件和递归删除文件夹 */ function my_del($path)//自定义my_del函数,函数有一个参数($path).当调用my_del( ...
- 关于win7下安卓开发环境的搭建
一.下载安装JDK(不用配置环境变量) 1.先卸载原来存在的JDK 控制面板-卸载程序-有两个软件(Java SE Development Kit 8 Update 101(64-bit)和Java ...