深入linux网络编程(一):同步IO
2013-05-28 16:20
288 查看
作者:yurunsun@gmail.com
新浪微博@孙雨润 新浪博客
CSDN博客日期:2012年11月16日
![](http://img.my.csdn.net/uploads/201304/11/1365669282_1062.gif)
不要在原子上下文中sleep
无法保证精确的睡眠时间
只有在确定其他进程/内核会唤醒自己时,才能睡眠
![](http://img.my.csdn.net/uploads/201303/17/1363522057_6498.jpg)
4.1
这是socket地址的基本数据结构:
4.2
第一个参数是socket协议族,包括
第二个参数是socket类型,有
第三个参数protocol为socket指定了协议,正常情况下对于一个给定的协议族,只有一个支持的协议,所以填0即可。特殊情况参见文档。
4.3
当socket通过socket函数创建好之后,他存在在一个地址协议族的名字空间中,但是没有为他分配地址,
这里
错误码见文档。
4.4
4.5
第一个参数sockfd就是刚刚使用socket函数创建、使用bind绑定本地地址、并使用listen标记为被动的那个sockfd。
第二个参数sockaddr指针指向一个客户端地址的结构体。
第三个参数addrlen是一个传出的参数,必须先用sizeof(sockaddr)初始化,函数返回时会将其赋值成客户端地址这个结构体的真实大小。如果客户端地址结构体的大小大于sizeof(sockaddr),那么会被阶段,并在addrlen中带回真正的大小。
如果连接等待队列中没有连接请求,默认情况下accept()会阻塞,直到有新的客户端连接出现;如果对socket指定了非阻塞,那么会返回错误码EAGAIN 或者EWOULDBLOCK
4.6
注意到上边代码中使用了
其中:
![](http://img.my.csdn.net/uploads/201303/17/1363522083_2295.gif)
这种模式极为少用。
新浪微博@孙雨润 新浪博客
CSDN博客日期:2012年11月16日
1. IO模型
IO分为同步、异步,阻塞、非阻塞,两两组合成4种模型。![](http://img.my.csdn.net/uploads/201304/11/1365669282_1062.gif)
2. 同步阻塞IO
2.1 阻塞的原因
一个常见的问题是IO对请求没有准备好:例如调用读请求的时候可能设备上没有数据,但是将来可能有;调用写请求时可能舍妹没有准备好接收数据,一会儿可能buffer清空就好了。调用过程一般不去理会这些问题,如果程序员仅仅要求在请求返回时工作做好,那么驱动设备就应该阻塞这个请求的进程,使他陷入睡眠状态。2.2 什么是睡眠
当一个进程处于睡眠态, 意味着它被移除调度队列,直到这个状态被改变之前,CPU都不会处理这个进程。有几个注意事项:不要在原子上下文中sleep
无法保证精确的睡眠时间
只有在确定其他进程/内核会唤醒自己时,才能睡眠
2.3 同步阻塞IO的编程模型
![](http://img.my.csdn.net/uploads/201303/17/1363522057_6498.jpg)
3. 同步阻塞IO的网络编程代码示例
/* A simple server in the internet domain using TCP The port number is passed as an argument */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> void error(const char *msg) { perror(msg); exit(1); } int main(int argc, char *argv[]) { int sockfd, newsockfd, portno; socklen_t clilen; char buffer[256]; struct sockaddr_in serv_addr, cli_addr; int n; if (argc < 2) { fprintf(stderr,"ERROR, no port provided\n"); exit(1); } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); bzero((char *) &serv_addr, sizeof(serv_addr)); portno = atoi(argv[1]); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(portno); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) error("ERROR on binding"); listen(sockfd,5); clilen = sizeof(cli_addr); newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); if (newsockfd < 0) error("ERROR on accept"); bzero(buffer,256); n = read(newsockfd,buffer,255); if (n < 0) error("ERROR reading from socket"); printf("Here is the message: %s\n",buffer); n = write(newsockfd,"I got your message",18); if (n < 0) error("ERROR writing to socket"); close(newsockfd); close(sockfd); return 0; }
4. API解释
4.1 sockaddr_in
这是socket地址的基本数据结构:struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ }; 一个IP socket地址被定义为一个IP地址和16位的端口,基本IP协议不支持端口号,而是由上层协议udp/tcp来实现。
4.2 socket
函数
int sockfd = socket(int socket_family, int socket_type, int protocol);
socket函数用来创建一个socket文件描述符。
第一个参数是socket协议族,包括
AF_INET, AF_IPX, AF_PACKET等,一般选择
AF_INET,具体含义请参考文档。
第二个参数是socket类型,有
SOCK_STREAM(TCP)和SOCK_DGRAM(UDP等等。除此之外,这个参数还有第二层含义,来描述socket的其他表现:
SOCK_NONBLOCK // 阻塞非阻塞(后面详细解释) SOCK_CLOEXEC // 当开辟其他进程调用exec()族函数时,调用前为其释放对应的文件描述符。(跟网络编程关系不大)
第三个参数protocol为socket指定了协议,正常情况下对于一个给定的协议族,只有一个支持的协议,所以填0即可。特殊情况参见文档。
4.3 bind
函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
当socket通过socket函数创建好之后,他存在在一个地址协议族的名字空间中,但是没有为他分配地址,
bind就是将一个地址分配给socket描述符。
这里
sa_data没有用,完全是为了将
sockaddr_in强制转换时内存大小一样,不会报错。
错误码见文档。
4.4 listen
函数
int listen(int sockfd, int backlog);
listen函数将这个socket标记为被动socket,意思是可以用
accept来接收别人的连接请求。
backlog参数指定了等待连接队列的最大长度,当队列满了之后,新的客户端连接会收到一个
ECONNREFUSED连接错误。
4.5 accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函数用在面向连接的socket类型,例如
SOCK_STREAM, SOCK_SEQPACKET,它会将请求等待队列的第一个连接请求提取出来,创建一个新的连接socket,返回这个socket的fd。
第一个参数sockfd就是刚刚使用socket函数创建、使用bind绑定本地地址、并使用listen标记为被动的那个sockfd。
第二个参数sockaddr指针指向一个客户端地址的结构体。
第三个参数addrlen是一个传出的参数,必须先用sizeof(sockaddr)初始化,函数返回时会将其赋值成客户端地址这个结构体的真实大小。如果客户端地址结构体的大小大于sizeof(sockaddr),那么会被阶段,并在addrlen中带回真正的大小。
如果连接等待队列中没有连接请求,默认情况下accept()会阻塞,直到有新的客户端连接出现;如果对socket指定了非阻塞,那么会返回错误码EAGAIN 或者EWOULDBLOCK
4.6 read/recv
与write/send
int recv(int sockfd,void *buf,int len,int flags); int send(int sockfd,void *buf,int len,int flags);
注意到上边代码中使用了
read,实际上也可以使用
recv,后者提供了第四个参数flag,用来标记一些socket的状态:
MSG_DONTROUTE:不查找路由表 MSG_OOB:接受或发送带外数据 MSG_PEEK:查看数据,并不从系统缓冲区移走数据 MSG_WAITALL :等待任何数据
其中:
MSG_DONTROUTE:是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.这个标志一般用网络诊断和路由程式里面。
MSG_OOB:表示能够接收和发送带外的数据.关于带外数据我们以后会解释的.
MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容。这样下次读的时候,仍然是相同的内容。一般在有多个进程读写数据时能够使用这个标志。
MSG_WAITALL:是recv函数的使用标志,表示等到任何的信息到达时才返回。使用这个标志的时候recv会一直阻塞,直到指定的条件满足,或是发生了错误。 1)当读到了指定的字节时,函数正常返回,返回值等于len 2)当读到了文档的结尾时,函数正常返回.返回值小于len 3)当操作发生错误时,返回-1,且配置错误为相应的错误号(errno).
write/send的区别同理。
5. 同步非阻塞IO
同步非阻塞IO是刚刚同步阻塞IO的一个聊胜于无的变种,设备以非阻塞方式打开,IO操作不成功会返回一个错误代码。关键问题是我们无法知道设备何时准备就绪能够操作成功,因此大多数情况不得不在循环中一遍一遍查询。![](http://img.my.csdn.net/uploads/201303/17/1363522083_2295.gif)
这种模式极为少用。
相关文章推荐
- 深入linux网络编程(一):同步IO
- nginx的io复用、阻塞非阻塞、同步异步、apache与nginx的区别
- IO模型(同步,异步,阻塞,非阻塞)
- 【IO进程】守护进程,线程创建、同步与互斥
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
- 同步与异步IO、阻塞与非阻塞IO(转载)
- 【转载】高性能IO设计 & Java NIO & 同步/异步 阻塞/非阻塞 Reactor/Proactor
- 关于IO的同步,异步,阻塞,非阻塞
- JavaIO之-BIO(同步阻塞线程)
- [转] 小议同步IO :fsync与fdatasync
- 同步阻塞IO
- IO - 同步,异步,阻塞,非阻塞
- IO - 同步,异步,阻塞,非阻塞
- java IO 模型 阻塞/非阻塞 同步/异步
- 网络IO之阻塞、非阻塞、同步、异步总结
- 同步文件IO和异步文件IO
- IO - 同步,异步,阻塞,非阻塞
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
- IO中同步、异步与阻塞、非阻塞的区别
- 网络IO模型解析:同步IO和异步IO,阻塞IO和非阻塞IO的对比分析