1. Linuxsocket的简介
在linux支持select模式,poll模式,在内核2.6版本以后支持epoll模式;
epoll模式的优点:
A:支持进程打开的最大socket数据
B:IO效率不随FD数目增加而线性下降
C:使用mmap加速内核与用户空间的消息传递。
D:内核微调
2. socket的属性(例如:设置为非阻塞模式)
1: /*设置socket为非阻塞模式
2: * window: ioctlsocket()
3: * linux: fcntl(), (头文件fcnt.h)
4: */
5: int set_nonblock(int fd)
6: {
7: int opts;
8: opts=fcntl(fd, F_GETFL); // 获得socket的属性
9: if (opts < 0)
10: return -1;
11:
12: opts = opts | O_NONBLOCK;
13: if(fcntl(fd, F_SETFL, opts) < 0) //设置socket的属性
14: return -1;
15:
16: return 0;
17: }
3.socket的缓冲区的扩充
int nRecvBufferLen=2*1024*1024; //设置为2M的缓冲区
setsockopt(s,SOL_SOCKET,SO_RCVBUF, (const char*)&nRecvBufferLen, sizeof(nRecvBufferLen));
4 sockaddr 和 sockaddr_in 的关系和对比
1: //sockaddr结构体:缺陷是sa_data把目标地址和端口信息混在一起了
2: struct sockaddr {
3: unsigned short sa_family; //通信类型: AF_INET 或 UNIX
4: char sa_data[14]; // 14字节,包含套接字中的目标地址和端口信息
5: };
6:
7: //sockaddr_in 结构体:解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
8: struct sockaddr_in {
9: short int sin_family;
10: unsigned short int sin_port; //端口(网络字节顺序)
11: struct in_addr sin_addr; //IP地址(网络字节顺序)
12: unsigned char sin_zero[8];
13: }
14:
15: //注ip地址的结构
16: struct in_addr { unsigned long s_addr; }
sockaddr 和 sockaddr_in的相互关系:
1.一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数
2.sockaddr_in用于socket定义和赋值
3.sockaddr用于函数参数
NBO网络字节顺序 和 HBO本机字节顺序 的转换
inet_addr() 将字符串转换为NBO网络地址
inet_ntoa () 将NBO地址转化成字符串点数格式
htons() 将short类型转换为网络类型的端口号
ntohs() 将NBO类型的端口转换为short类型的变量
1: //代码例子
2: struct sockaddr_in cliaddr;
3: bzero(&cliaddr,sizeof(cliaddr)); //网络地址的初始化
4: ina.sin_family=AF_INET; //INET通信类型
5: ina.sin_port = htons(23); //指定端口,将host的变量转换为NBO的端口号
6: ina.sin_addr.s_addr = inet_addr("132.241.5.10"); //将字符串转换为NBO地址
7:
8: char* szIp = inet_ntoa(ina.sin_addr);//inet_ntoa 将NBO地址转化成字符串点数格式
9: short port = ntohs(ina.sin_port);//ntohs 将NBO端口转换为short变量
5.socket的创建和设置
socket创建和设置,及其设置(tcp、udp流程类似)
1: //建立一个侦听socket
2: int create_listener(int port)
3: {
4: int fd;
5:
6: //创建一个inet类型的socket
7: if ((fd = socket(AF_INET, SOCK_STREAM, 0)) <= 0)
8: return -1;
9:
10:
11: int opt=1;
12:
13: /* SO_REUSEADDR,只定义一个套接字在一个端口上进行监听,
14: * 如果服务器出现意外而导致没有将这个端口释放,
15: * 那么服务器重新启动后,你还可以用这个端口,因为你已经规定可以重用了,
16: 如果你没定义的话,你就会得到提示,ADDR已在使用中
17: */
18: setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));
19: set_nonblock(fd);
20:
21:
22: /* sockaddr结构体的缺陷:sa_data把目标地址和端口信息混在一起了
23: * sockaddr_in 结构体: 把port和addr 分开储存在两个变量中
24: * sockaddr 和 sockaddr_in的相互关系
25: */
26: struct sockaddr_in sin;
27: memset(&sin, 0, sizeof(struct sockaddr_in));
28: sin.sin_family = AF_INET;
29: sin.sin_port = htons(port); // 指定port端口
30: sin.sin_addr.s_addr = INADDR_ANY; // ip地址不做绑定使用机器默认的ip地址
31: if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0)
32: {
33: close(fd);
34: return -1;
35: }
36:
37:
38:
39: // 开始侦听该端口
40: if (listen(fd, 32) != 0)
41: {
42: close(fd);
43: return -1;
44: }
45:
46: return fd;
47: }
6. socket的接收数据
socket接收数据要注意:
1. 是否是阻塞模式;如果是阻塞模式,没有读到数据会持续等待;非阻塞则返回;如果要根据错误代码和接收模式及其收到数据的个数,来判断是客户端已经断开?网络错误?
2. udp如果有数据则就是一整包数据,而tcp是流会出现粘包和半包数据的情况
1: //socket数据接收,可以Window可以linux
2: while(rs)
3: {
4: //接收数据,默认接收sizeof(buf)大小,实际接收buflen大小
5: buflen = recv(sock_fd, buf, sizeof(buf), 0);
6: if(buflen < 0)
7: {
8: // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读;在这里表示该事件已做处理
9: if(errno == EAGAIN)
10: break;
11: else
12: return;//表示有错误发生
13: }
14: else if(buflen == 0)
15: {
16: // 这里表示对端的socket已正常关闭.
17: }
18:
19:
20: if(buflen == sizeof(buf)
21: rs = 1; // 需要再次读取
22: else
23: rs = 0;
24: }
7. socket的发送数据
◇.send函数最后一个参数
window: 一般是0
linux: 最好设置为MSG_NOSIGNAL;表示出错后不向系统发信号,否则程序会退出!
1: /* socket发送数据,可以在Window下,可以在linux下
2: *
3: * 该代码是采用阻塞模式下,完整的发送一个缓冲中的数据
4: * 如果缓冲区满,则阻塞线程等待缓冲区有空间再发送
5: * 如果网络错误,或socket错误,会立即返回-1
6: */
7: int socket_send(int sockfd, const char* buffer, size_t buflen)
8: {
9: int tmp;
10: size_t total = buflen;
11: const char *p = buffer;
12:
13: while(1)
14: {
15: /* window: 一般是0
16: * linux: 最好设置为MSG_NOSIGNAL;表示出错后不向系统发信号,否则程序会退出!
17: */
18: tmp = send(sockfd, p, total, 0);
19: if(tmp < 0)
20: {
21: //当进程收到信号会中断正在进行的系统调用、区处理信号,处理完系统返回-1且errno==Eintr;
22: //所以可continue继续执行
23: if(errno == EINTR)
24: continue;
25:
26:
27: // Eagain表示写缓冲队列已满, usleep后继续发送
28: if(errno == EAGAIN)
29: {
30: usleep(1000);
31: continue;
32: }
33:
34:
35: return -1;
36: }
37:
38:
39: if((size_t)tmp == total)
40: return buflen;
41:
42:
43: total -= tmp;
44: p += tmp;
45: }
46:
47:
48: return tmp;
49: }
8. socket的select模式
9. socket的epoll模式
int epoll_ctl(int epfd, int op, int fd, epoll_event *event);
op:Add,Mod,Del
event {events,epoll_da
events:epollIN/out/pri/err/hup/et
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
ET:高速模式,支持无阻塞;当fd从未就绪变成就绪epoll将通知你,然后它会假设你已经知道了该通知且不会再次发出通知、知道你做了操作将fd变为未就绪状态;所以如果你一直对某个fd没有操作,那么内核将一直不发出该fd的通知!
LT:同ET相反,它会一直发出fd的通知!
ET模式:在epollIN事件时,recv返回的大小等于请求大小,那么很有可能缓冲区还有数据未读完,也意味着该次事件没有处理完,还需要再次读取; 参考数据完整接收
ET模式:由于epoll是无阻塞那么send虽然返回但是数据并没有真正发出去,当缓冲区满后会产生Eagain错误,同时不会理此次待发送的数据,因此封装一个socket_send函数来处理该问题,如果返回eagain就阻塞在该函数中[EAGAIN 另外一个名字是 EWOULDBLOCK] 参考数据的完整发送
1: /* 这是一个简单的epoll模式的程序,
2: *
3: */
4: int main()
5: {
6: //1. 创建epoll描述符, 可接受5000个连接
7: epoll_fd = epoll_create(5000);
8:
9: //2. 创建、绑定和侦听socket; 并且将它放到epoll上面开始侦听数据到达和connect连接
10: listen_fd = create_listener(port);
11: epoll_event ev;
12: ev.data.fd = cfd;
13: ev.events = EPOLLIN | EPOLLET;
14: epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cfd, &ev);//检查cfd上面的数据到达
15:
16:
17: //3.创建线程、并脱离创建者;在线程中检索epoll上面的触发的socket
18: pthread_attr_init(&attr);
19: pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
20: pthread_create(&tid, &attr, epoll_function, NULL);
21: }
22:
23:
24: // 专门进行侦听epoll的线程函数
25: void *epoll_function(void *p)
26: {
27: while ( 1)
28: {
29: // 等待epoll上面的事件触发,然后将他们的fd检索出来
30: struct epoll_event events[EPOLL_EVENT_MAX];
31: int evn_cnt = epoll_wait(epfd, events, EVENTSIZE , -1);
32:
33: // 循环处理检索出来的events队列中的每个fd
34: for (int i=0; i<wait_cnt; i++)
35: {
36: if (events[i].data.fd == listen_fd) // 有客户端connect过来
37: {
38: struct sockaddr_in caddr;
39: socklen_t clen = sizeof(caddr);
40:
41: // 接收连接
42: int connfd = accept(listen_, (sockaddr *)&caddr, &clen);
43: if (connfd < 0)
44: continue;
45:
46: // 将新接收的连接的socket放到epoll上面
47: struct epoll_event ev;
48: ev.data.fd = connfd;
49: ev.events = EPOLLIN|EPOLLET;
50: epoll_ctl(epoll_, EPOLL_CTL_ADD, connfd, &ev);
51: }
52: else if (events[i].events & EPOLLIN) // 有数据到达
53: {
54: //接收数据
55: recv(event[i].data.fd, buffer, sizeof(buffer), 0);
56: }
57: }
58: }
59: }
10.Window和linux的socket的不同之处:
11.创建线程
1: // 创建脱离创建者的线程
2: pthread_t tid;
3: pthread_attr_t attr;
4: pthread_attr_init(&attr);
5:
6: // 创建线程PTHREAD_CREATE_DETACHED(分离线程),PTHREAD _CREATE_JOINABLE(非分离线程)
7: // 分离线程是不能join的,非分离线程可以join
8: // 一般来说服务器大多采用分离线程
9: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
10:
11: //创建线程;指定线程属性、指定线程函数
12: if (pthread_create(&tid, &attr, serv_epoll, NULL) != 0)
13: {
14: fprintf(stderr, "pthread_create failed\n");
15: return -1;
16: }
http://blog.163.com/miky_sun/blog/static/3369405200992593735641/