Linux 网络编程笔记(2)——socket 编程
2017-05-28 00:32
381 查看
socket 可以看作是用户进程与内核网络协议栈的编程接口
既可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信
listenfd 是被动套接字,可以用来接受连接。
accept 从已完成连接队列中取出一个队列头,得到一个新套接字 connfd。
connfd 是主动套接字,不能用来接受连接。
服务端和客户端的 ip 和 port 四元组表示 一个连接。
REUSEADDR 可以解决“关闭一个服务器后,由于 TIME_WAIT 无法马上重启服务器的问题”。在 TIME_WAIT 还没消失的时候允许重启。
接收方不能保证读操作的时候能返回多少个字节。
UDP 是基于消息的传输服务,传输的是报文,有边界。
能保证接收方每次返回一条消息。
几种产生可能性
应用进程缓冲区的数据拷贝到套接口发送缓冲区时,如果应用层缓冲区一条消息大小超过套接口发送缓冲区的大小时,就有可能产生粘包问题。(消息被分割)
TCP 传输的段有最大限制(MSS),超过 MSS 就会被分割 。
链路层也有最大传输限制,在 IP 网络层会分组。
TCP 流量控制、拥塞控制等。
TCP 延迟发送机制。
定长包
包尾加上 \r\n (ftp)(要考虑消息本身具有 \r\n 这种情况)
包头加上包体长度
更复杂的应用层协议
可以自定义一种协议,包头 4 个字节表示包体长度(作为定长包的长度),后面的字节作为包体。
发送前,先用
然后再
用
pid_t
pid > 0 时,只等待进程 ID 等于 pid 的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束, waitpid就会一直等下去。
pid = -1时,等待任何一个子进程退出,没有任何限制,此时waitpid 和 wait的作用一模一样。
pid = 0 时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会对它做任何理睬。
pid<-1 时,等待一个指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。
options
Linux 中只支持 WNOHANG(不挂起、非阻塞) 和 WUNTRACED
可以往一个已经接收 FIN 的套接字中写,接收到 FIN 仅仅代表对方不再发送数据。
在收到 RST 段之后,如果再调用 write 就会产生 SIGPIPE 信号, 对于这个信号的处理我们通常忽略即可。(对端 close 之后,如果本端再 write,第一次会产生 RST,第二次产生 SIGPIPE 信号)
五种 I/O 模型
1.阻塞 I/O
2.非阻塞I/O
3.I/O多路复用
4.信号驱动I/O(得到信号时,仅仅表明有数据来,应用程序还要recv)
5.异步I/O(用户得到信号时,内核已经把数据推到了用户空间)
信号是异步处理的一种方式。
基于消息的数据传输服务
不可靠
一般情况 UDP 更高效
UDP 不需要 bind,而是再第一次 sendto 和 recvfrom 的时候自动绑定
既可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信
listenfd 是被动套接字,可以用来接受连接。
accept 从已完成连接队列中取出一个队列头,得到一个新套接字 connfd。
connfd 是主动套接字,不能用来接受连接。
服务端和客户端的 ip 和 port 四元组表示 一个连接。
REUSEADDR 可以解决“关闭一个服务器后,由于 TIME_WAIT 无法马上重启服务器的问题”。在 TIME_WAIT 还没消失的时候允许重启。
流协议与粘包问题
tcp 是字节流协议,无边界。接收方不能保证读操作的时候能返回多少个字节。
UDP 是基于消息的传输服务,传输的是报文,有边界。
能保证接收方每次返回一条消息。
几种产生可能性
应用进程缓冲区的数据拷贝到套接口发送缓冲区时,如果应用层缓冲区一条消息大小超过套接口发送缓冲区的大小时,就有可能产生粘包问题。(消息被分割)
TCP 传输的段有最大限制(MSS),超过 MSS 就会被分割 。
链路层也有最大传输限制,在 IP 网络层会分组。
TCP 流量控制、拥塞控制等。
TCP 延迟发送机制。
粘包解决方案
本质上是要在应用层维护消息与消息的边界。定长包
包尾加上 \r\n (ftp)(要考虑消息本身具有 \r\n 这种情况)
包头加上包体长度
更复杂的应用层协议
readn()和
writen()的封装,封装后只有接收到指定字节数才会跳出循环,可用于发送定长包(比方说不管数据多长,一律发送 1024 个字节,有可能浪费网络资源)。
ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char*)buf; while (nleft > 0) { if (nread = read(fd, bufp, nleft) < 0) { if (errno = EINTR) // 被中断 continue; else return -1; // 其他错误 } else if (nread == 0) { return count - nread; // 结束,返回读取到的字节数 } else { bufp += nread; nleft -= nread; } } return count; } ssize_t writen(int fd, const char *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char*)buf; while (nleft > 0) { if (nwritten = write(fd, bufp, nleft) < 0) { if (errno = EINTR) continue; else return -1; } else if (nwritten == 0) continue; else { bufp += nwritten; nleft -= nwritten; } } }
可以自定义一种协议,包头 4 个字节表示包体长度(作为定长包的长度),后面的字节作为包体。
struct packet { int len; chat buf[1024]; } //... struct packet sendbuf; //... struct packet recvbuf;
发送前,先用
n = strlen(sendbuf.buf); sendbuf.len = htol(n)填充
sendbuf.len(要注意字节序的问题)
然后再
writen(sock, sendbuf, 4+n)n 为包体长度。
MSG_PEEK 读取数据但不清除缓存
read_peek()
封装了 MSG_PEEK
ssize_t recv_peek(int sockfd, void *buf, size_t len) { while(1) { int ret = recv(sockfd, buf, len, MSG_PEEK); if(ret == -1 && errno == EINTR) continue; else return ret; } }
用 read_peek()
和read_n()
实现
readline():先
read_peek()看有没有’\n’,有的话看在第几个,然后只读到这个字节。
ssize_t readline(int socket, void *buf, size_t maxline) { int ret; int nread; char *bufp; int nleft = maxline; while(1) { ret = recv_peek(socket, bufp, nleft); // 偷窥数据但不从缓冲区清除 // ret 为缓冲区中的数据量 if(ret < 0) return ret; else if(ret == 0) return ret; nread = ret; for(int i = 0; i < nread; ++i) { if(bufp[i] == '\n') // 有 '\n',在第 i+1 位 { readn(socket, bufp, i+1); if(ret != i+1) // 没有读取到 i+1 个字节的数据 exit(EXIT_FAILURE;) return ret; } } // 以下为没 '\n' 的处理方法 nread = ret; if(nread > nleft) exit(EXIT_FAILURE); ret = readn(socket, bufp, nread); if(ret != nread) exit(EXIT_FAILURE); bufp += nread; nleft -= nread; } }
recv()
和read()
的区别
ssize_t read(int fd, void *buf, size_t count)
ssize_t recv(int sockfd, void *buf, size_t count, int flags)
read()处理的 fd 可以是任何 fd,
recv()的 fd 是 sockfd。
recv()多出来一个参数 flags 可以传入 MSG_PEEK 等参数。
僵尸进程
四种僵尸进程避免方式:
1.wait和waitpid函数 2.signal安装处理函数(交给内核处理) 3.signal忽略SIGCHLD信号(交给内核处理) 4.fork两次
pid_t wait(int *status)只能阻塞,一旦 wait 之后,父进程将会阻塞自己直到子进程结束运行。当我们不关心子进程的退出状态,我们可以传入空指针。
pid_t waitpid(pid_t pid,int *status,int options)
pid_t
pid > 0 时,只等待进程 ID 等于 pid 的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束, waitpid就会一直等下去。
pid = -1时,等待任何一个子进程退出,没有任何限制,此时waitpid 和 wait的作用一模一样。
pid = 0 时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会对它做任何理睬。
pid<-1 时,等待一个指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。
options
Linux 中只支持 WNOHANG(不挂起、非阻塞) 和 WUNTRACED
可以往一个已经接收 FIN 的套接字中写,接收到 FIN 仅仅代表对方不再发送数据。
在收到 RST 段之后,如果再调用 write 就会产生 SIGPIPE 信号, 对于这个信号的处理我们通常忽略即可。(对端 close 之后,如果本端再 write,第一次会产生 RST,第二次产生 SIGPIPE 信号)
signal(SIGPIPE, SIG_IGN);
五种 I/O 模型
1.阻塞 I/O
2.非阻塞I/O
3.I/O多路复用
4.信号驱动I/O(得到信号时,仅仅表明有数据来,应用程序还要recv)
5.异步I/O(用户得到信号时,内核已经把数据推到了用户空间)
信号是异步处理的一种方式。
UDP
无连接基于消息的数据传输服务
不可靠
一般情况 UDP 更高效
UDP 不需要 bind,而是再第一次 sendto 和 recvfrom 的时候自动绑定
相关文章推荐
- Linux网络笔记二(UDP Socket 编程)
- Linux程序设计学习笔记----Socket网络编程基础之TCP/IP协议簇
- Linux网络编程学习笔记-socket编程3--5
- Linux网络编程学习笔记--socket编程4--7
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- Linux/POSIX Socket编程 笔记1及反思
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- linux网络编程--socket(2)
- Linux下Socket网络编程,文件传输,数据传输的C语言例子
- Linux网络编程socket错误分析
- Linux网络编程socket错误分析
- [转]Linux下Socket网络编程,文件传输,数据传输的C语言例子
- Linux Socket 网络编程 (IBM网站)
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- Linux Socket 网络编程 基于GTK+ 的多线程实现的局域网通信软件
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- Linux 网络编程笔记
- linux网络编程笔记
- C#网络编程笔记(1) -- TCP Socket通信基本过程和思路
- Linux下Socket网络编程,文件传输,数据传输的C语言例子