网络编程
网络编程是什么?有什么用?如何实现?
写在前面
本篇仅作简单介绍,内容适合入门级别
文中相关api就不做过多介绍,不然字数就太多了,可自行去api手册了解
语言不保证严谨,内容也不保证完全,欢迎指正错误、补充
本文我已经尽量减少字数了,还是1w多,不知道打开会不会卡 : )
一、网络编程基础
一、为什么引入网络编程
学完多进程、进程间通信,我们发现这些通信方式都是在同一个主机上多个进程之间进行的,并不能实现跨主机通信。如果想要实现跨主机的通信方式,就要用到socket套接字通信方式,也就是本篇讲的内容。
二、网络基础
都涉及到网络了,肯定是需要一定的计算机网络知识,这里我只简单提及
三、网络编程基础
一、套接字
二、基于TCP面向连接的通信
网络通信方式有两种:基于BS模型(浏览器服务器模型)、基于CS模型(客户端服务器模型),本篇主要介绍CS模型,BS模型是http服务器用到的。
原理(几个api)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 服务器端: 1.socket()创建套接字用于连接 2.bind()为套接字绑定IP和端口号,方便连接 3.listen()将第一步创建的套接字设置成被动监听状态并在其中创建两个队列(半连接队列和已连接队列),因为socket创建的套接字只能主动连接 4.accept()处理已连接队列中已建立的连接,并创建一个新的套接字用于和客户端套接字通信 5.send()/recv()进行数据传输 6.close()关闭套接字
客户端 1.socket()创建套接字用于连接 2.bind()为套接字绑定IP和端口号,方便连接(客户端可选是否手动绑定,不手动绑定时,系统会自动分配) 3.connect()将套接字连接到给定的地址上 4.send()/recv()进行数据传输 5.close()关闭套接字
|
TCP服务器端和客户端实现
服务器端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "0.0.0.0"
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_STREAM, 0); if(sfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
if(listen(sfd, 128) == -1) { perror("listen error"); return -1; } printf("listen success\n");
struct sockaddr_in cin; socklen_t socklen = sizeof(cin); int newfd = accept(sfd, (struct sockaddr*)&cin, &socklen); if(newfd == -1) { perror("accept error"); return -1; } printf("[%s:%d]:连接成功!!\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
char rbuf[128] = ""; while(1) { bzero(rbuf, sizeof(rbuf)); int res = recv(newfd, rbuf, sizeof(rbuf)-1, 0); if(res == 0) { printf("对端已下线\n"); break; } printf("收到消息:%s\n", rbuf); strcat(rbuf, " :)"); if(send(newfd, rbuf, strlen(rbuf), 0) == -1) { perror("send error"); return -1; } printf("发送成功\n"); }
close(newfd); close(sfd); return 0; }
|
客户端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "127.0.0.1"
#define CLI_PORT 9999 #define CLI_IP "127.0.0.1"
int main(int argc, const char *argv[]) { int cfd = socket(AF_INET, SOCK_STREAM, 0); if(cfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in cin; cin.sin_family = AF_INET; cin.sin_port = htons(CLI_PORT); cin.sin_addr.s_addr = inet_addr(CLI_IP); if(bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) == -1) { perror("connect error"); return -1; } printf("连接服务器成功\n");
char wbuf[128] = ""; while(1) { bzero(wbuf, sizeof(wbuf)); fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = '\0';
if(send(cfd, wbuf, strlen(wbuf), 0) == -1) { perror("send error"); return -1; } printf("发送成功\n");
bzero(wbuf, sizeof(wbuf)); int res = recv(cfd, wbuf, sizeof(wbuf)-1, 0); if(res == 0) { printf("服务器已下线\n"); break; } printf("收到服务器消息:%s\n", wbuf); }
close(cfd); return 0; }
|
三、基于UDP面向无连接的通信
udp通信是面向无连接的,不可靠的,尽最大努力传输的通信
传输过程中可能出现数据丢失、重复、失序
创建套接字时,使用的传输层名称为SOCK_DGRAM
1 2 3 4 5 6 7 8 9 10
| 服务器端: 1.socket():创建用于连接的套接字 2.bind():绑定ip和端口号 3.recvfrom()/sendto():收发数据 4.close():关闭套接字 客户端: 1.socket():创建用于连接的套接字 2.bind():绑定ip和端口号 3.recvfrom()/sendto():收发数据 4.close():关闭套接字
|
服务器端:
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 47 48 49 50 51 52 53 54 55
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "0.0.0.0" #define CLI_PORT 9999 #define CLI_IP "127.0.0.1"
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
char rbuf[128] = ""; struct sockaddr_in cin; socklen_t socklen = sizeof(cin);
while(1) { bzero(rbuf, sizeof(rbuf)); if(recvfrom(sfd, rbuf, sizeof(rbuf)-1, 0, (struct sockaddr*)&cin, &socklen) == -1) { perror("recvfrom error"); return -1; } printf("收到[%s:%d]消息:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf);
strcat(rbuf, " :)"); sendto(sfd, rbuf, strlen(rbuf), 0, (struct sockaddr*)&cin, sizeof(cin)); printf("发送成功\n"); }
close(sfd); return 0; }
|
客户端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "127.0.0.1" #define CLI_PORT 9999 #define CLI_IP "127.0.0.1"
int main(int argc, const char *argv[]) { int cfd = socket(AF_INET, SOCK_DGRAM, 0); if(cfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in cin; cin.sin_family = AF_INET; cin.sin_port = htons(CLI_PORT); cin.sin_addr.s_addr = inet_addr(CLI_IP); if(bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
char wbuf[128] = ""; struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP);
while(1) { bzero(wbuf, sizeof(wbuf)); fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = '\0';
if(sendto(cfd, wbuf, strlen(wbuf), 0, (struct sockaddr*)&sin, sizeof(sin)) == -1) { perror("sendto error"); break; } printf("发送成功\n");
bzero(wbuf, sizeof(wbuf)); if(recvfrom(cfd, wbuf, sizeof(wbuf)-1, 0, NULL, NULL) == -1) { perror("recvfrom error"); return -1; } printf("收到消息:%s\n", wbuf); }
close(cfd); return 0; }
|
二、TCP并发服务器
上面写的代码样例中,有一些问题,TCP服务器端并不能连接多个客户端,接下来就是要引入并发操作来解决该问题
一、循环服务器
如果你已经理解了基本的TCP模型,那么你肯定能看出来问题就出在了accept只进行了一次,那好解决啊,循环不就行了吗?试试看再说
TCP服务器端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "0.0.0.0"
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_STREAM, 0); if(sfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
if(listen(sfd, 128) == -1) { perror("listen error"); return -1; } printf("listen success\n");
struct sockaddr_in cin; socklen_t socklen = sizeof(cin); int newfd = accept(sfd, (struct sockaddr*)&cin, &socklen); if(newfd == -1) { perror("accept error"); return -1; } printf("[%s:%d]:连接成功!!\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
char rbuf[128] = ""; while(1) { bzero(rbuf, sizeof(rbuf)); int res = recv(newfd, rbuf, sizeof(rbuf)-1, 0); if(res == 0) { printf("对端已下线\n"); break; } printf("收到消息:%s\n", rbuf); strcat(rbuf, " :)"); if(send(newfd, rbuf, strlen(rbuf), 0) == -1) { perror("send error"); return -1; } printf("发送成功\n"); }
close(newfd); close(sfd); return 0; }
|
就是这样,加一个while循环,似乎就解决了?这么简单,会出问题的吧?
执行代码后尝试连接多个服务端,问题出现了:与客户端1进行数据交换时,并不能处理客户端2的连接请求,因为在recv处阻塞了,除非客户端1下线。
这还是没解决问题,但是之前学过了实现并发可以用多进程、多线程,试试吧。
二、多进程实现并发
主进程用于完成对客户端的连接请求,子进程完成与客户端的数据交换。
是可靠多了,但是难点就在资源回收上了
- 父进程阻塞回收(wait)时肯定出问题,因为第一个子进程不退出,父进程就卡住了
- 那使用非阻塞回收(waitpid)吗?模拟一下,若仅仅有一个客户端,那父进程就会卡在accept处,即使子进程退出了,父进程也执行不到waitpid处了。
- 多进程中有一个信号的概念,我在多进程那篇也提到了自动回收僵尸进程,这里就可以使用
TCP服务器端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "0.0.0.0"
void handler(int signo) { if(signo == SIGCHLD) { while(waitpid(-1, NULL, WNOHANG) > 0); } }
int main(int argc, const char *argv[]) { if(signal(SIGCHLD, handler) == SIG_ERR) { perror("signal error"); return -1; }
int sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP);
if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
if (listen(sfd, 128) == -1) { perror("listen error"); return -1; } printf("listen success\n");
struct sockaddr_in cin; socklen_t socklen = sizeof(cin); while (1) {
int newfd = accept(sfd, (struct sockaddr *)&cin, &socklen); if (newfd == -1) { perror("accept error"); return -1; } printf("[%s:%d]:连接成功!!\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
pid_t pid = fork(); if (pid > 0) { close(newfd); } else if (pid == 0) {
char rbuf[128] = ""; while (1) { bzero(rbuf, sizeof(rbuf)); int res = recv(newfd, rbuf, sizeof(rbuf) - 1, 0); if (res == 0) { printf("对端已下线\n"); break; } printf("收到消息:%s\n", rbuf);
strcat(rbuf, " :)"); if (send(newfd, rbuf, strlen(rbuf), 0) == -1) { perror("send error"); return -1; } printf("发送成功\n"); }
close(newfd);
exit(EXIT_SUCCESS); } else { perror("fork error"); return -1; } } close(sfd); return 0; }
|
多进程可以使用,再试一下多线程吧
三、多线程实现并发
主线程用于处理客户端的连接请求,分支线程用于和客户端数据交换
同样是资源回收,将分支线程设置为分离态即可
TCP服务器端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "0.0.0.0"
void *task(void *arg) { int newfd = *(int *)arg; char rbuf[128] = ""; while (1) { bzero(rbuf, sizeof(rbuf)); int res = recv(newfd, rbuf, sizeof(rbuf) - 1, 0); if (res == 0) { printf("对端已下线\n"); break; } printf("收到消息:%s\n", rbuf);
strcat(rbuf, " :)"); if (send(newfd, rbuf, strlen(rbuf), 0) == -1) { perror("send error"); return NULL; } printf("发送成功\n"); }
close(newfd); pthread_exit(NULL); }
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP);
if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
if (listen(sfd, 128) == -1) { perror("listen error"); return -1; } printf("listen success\n");
struct sockaddr_in cin; socklen_t socklen = sizeof(cin); while(1) { } int newfd = accept(sfd, (struct sockaddr *)&cin, &socklen); if (newfd == -1) { perror("accept error"); return -1; } printf("[%s:%d]:连接成功!!\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
pthread_t tid = -1; if(pthread_create(&tid, NULL, &task, &newfd) != 0) { printf("pthread_create error\n"); return -1; }
pthread_detach(tid); close(sfd); return 0; }
|
四、IO多路复用
一、select
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
| #include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); 功能:阻塞等待文件描述符集合中是否有事件产生,如果有事件产生,则解除阻塞 参数1:文件描述符集合中,最大的文件描述符 加1 参数2、参数3、参数4:分别表示读集合、写集合、异常处理集合的起始地址 由于对于写操作而言,我们也可以转换读操作,所以,只需要使用一个集合就行 对于不使用的集合而言,直接填NULL即可 参数5:超时时间,如果填NULL表示永久等待,如果想要设置时间,需要定义一个如下结构体类型的变量,并将地址传递进去 struct timeval { long tv_sec; long tv_usec; }; and struct timespec { long tv_sec; long tv_nsec; }; 返回值: >0:成功返回解除本次阻塞的文件描述符的个数 =0:表示设置的超时时间,时间已经到达,但是没有事件事件产生 =-1:表示失败,置位错误码
void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set);
|
select实现TCP并发服务器:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "0.0.0.0"
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP);
if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
if (listen(sfd, 128) == -1) { perror("listen error"); return -1; } printf("listen success\n");
struct sockaddr_in cin; socklen_t socklen = sizeof(cin);
fd_set readfds, tempfds;
FD_ZERO(&readfds);
FD_SET(sfd, &readfds);
int maxfd = sfd; int newfd = -1; struct sockaddr_in cin_arr[1024];
while (1) { tempfds = readfds;
int res = select(maxfd + 1, &tempfds, NULL, NULL, NULL); if (res == -1) { perror("select error"); return -1; } else if (res == 0) { printf("time out!\n"); return -1; }
if (FD_ISSET(sfd, &tempfds)) { int newfd = accept(sfd, (struct sockaddr *)&cin, &socklen); if (newfd == -1) { perror("accept error"); return -1; } printf("连接成功!!\n");
cin_arr[newfd] = cin;
FD_SET(newfd, &readfds); if (maxfd < newfd) { maxfd = newfd; } }
for (int i = 4; i <= maxfd; i++) { if (FD_ISSET(i, &tempfds)) { char rbuf[128] = ""; bzero(rbuf, sizeof(rbuf)); int res = recv(newfd, rbuf, sizeof(rbuf) - 1, 0); if (res == 0) { printf("对端已下线\n"); close(i); FD_CLR(i, &readfds); for (int k = maxfd; k >= 0; i--) { if (FD_ISSET(k, &readfds)) { maxfd = k; break; } } continue; } printf("收到消息:%s\n", rbuf);
strcat(rbuf, " :)"); if (send(newfd, rbuf, strlen(rbuf), 0) == -1) { perror("send error"); return -1; } printf("发送成功\n");
close(newfd); } } }
close(sfd); return 0; }
|
实际上,select并不经常用,一是因为其文件描述符集合最多放1024个文件描述符,二是每次调用需复制整个文件描述符集合。但是跨平台或简单场景还是能用的。
二、poll
poll比select稍灵活了些,下面是相关api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); 功能:阻塞等待文件描述符集合中是否有事件产生,如果有,则解除阻塞,返回本次触发事件的文件描述符个数 参数1:文件描述符集合容器的起始地址,是一个结构体数组,结构体类型如下 struct pollfd { int fd; short events; short revents; }; 关于事件对应的位: POLLIN:读事件 POLLOUT:写事件 参数2:集合中文件描述符的个数 参数3:超时时间,负数表示永久等待,0表示非阻塞 返回值: >0:表示触发本次解除阻塞事件的文件描述符的个数 =0:表示超时 =-1:出错,置位错误码
|
接下来我们使用poll解决一下tcp客户端输入阻塞和接收数据阻塞冲突的问题
TCP客户端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "127.0.0.1"
#define CLI_PORT 9999 #define CLI_IP "127.0.0.1"
int main(int argc, const char *argv[]) { int cfd = socket(AF_INET, SOCK_STREAM, 0); if (cfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in cin; cin.sin_family = AF_INET; cin.sin_port = htons(CLI_PORT); cin.sin_addr.s_addr = inet_addr(CLI_IP); if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if (connect(cfd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("connect error"); return -1; } printf("连接服务器成功\n");
struct pollfd pfds[2]; pfds[0].fd = 0; pfds[0].events = POLLIN;
pfds[1].fd = cfd; pfds[1].events = POLLIN;
char wbuf[128] = ""; while (1) {
int res = poll(pfds, 2, -1); if (res == -1) { perror("poll error"); return -1; }
if (pfds[0].revents == POLLIN) { bzero(wbuf, sizeof(wbuf)); fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = '\0';
if (send(cfd, wbuf, strlen(wbuf), 0) == -1) { perror("send error"); return -1; } printf("发送成功\n"); }
if (pfds[1].revents == POLLIN) { bzero(wbuf, sizeof(wbuf)); int res = recv(cfd, wbuf, sizeof(wbuf) - 1, 0); if (res == 0) { printf("服务器已下线\n"); break; } printf("收到服务器消息:%s\n", wbuf); } }
close(cfd); return 0; }
|
poll基本不怎么用,虽然比select稍灵活(文件描述符集合没有大小限制),但效率也不怎么高
三、epoll
上面两种的效率都比较低,这个epoll实现的IO多路复用效率就高了,因为其改进了工作方式,但不能跨平台了,只能用于linux
sellect和poll都是基于线性结构进行检测集合,而epoll是基于树形结构(红黑树)完成管理检测集合的
select和poll检测时,随着集合的增大,效率会越来越低。epoll使用的是函数回调机制,效率较高。处理文件描述符的效率也不会随着文件秒数的增大而降低。函数回调就是不用一直询问,等待事件被检测到了,由事件方通知哪个文件描述符发生了,加入就绪队伍即可。一个例子就是:你的肚子饿了会自己通知你,不用你一直问。
和poll一样,epoll没有最大文件描述符的限制,仅仅受到程序能够打开的最大文件描述符数量限制
epoll只适用于linux平台,不能跨平台操作
每次调用select()或poll()时,都要把所有描述符从用户空间复制到内核空间,检查完再复制回用户空间。而epoll只需第一次调用epoll_ctl()注册描述符时,复制一次到内核。之后只有连接状态变化(比如有数据可读)时才更新
相关api
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <sys/epoll.h>
int epoll_create(int size); 功能:创建一个epoll实例,并返回该实例的句柄,是一个文件描述符 参数1:epoll实例中能够容纳的最大节点个数,自从linux 2.6.8版本后,size可以忽略,但是必须要是一个大于0的数字 返回值:成功返回控制epoll实例的文件描述符,失败返回-1并置位错误码
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 功能:完成对epoll实例的控制
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 功能:阻塞检测epoll实例中是否有文件描述符准备就绪,如果准备就绪了,就解除阻塞
|
epoll实现TCP并发服务器
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "0.0.0.0"
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP);
if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
if (listen(sfd, 128) == -1) { perror("listen error"); return -1; } printf("listen success\n");
struct sockaddr_in cin; socklen_t socklen = sizeof(cin);
int epfd = epoll_create(1); if (epfd == -1) { perror("epoll_create error"); return -1; }
struct epoll_event ev; ev.data.fd = sfd; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev);
struct epoll_event evs[1024]; int size = sizeof(evs) / sizeof(evs[0]);
while (1) { int num = epoll_wait(epfd, evs, size, -1); printf("触发的事件个数num = %d\n", num);
for (int i = 0; i < num; i++) { int fd = evs[i].data.fd; if (fd == sfd) { int newfd = accept(sfd, (struct sockaddr *)&cin, &socklen); if (newfd == -1) { perror("accept error"); return -1; } printf("[%s:%d]:连接成功!!\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
struct epoll_event ev; ev.data.fd = newfd; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &ev); } else { char rbuf[128] = ""; bzero(rbuf, sizeof(rbuf)); int res = recv(fd, rbuf, sizeof(rbuf) - 1, 0); if (res == 0) { printf("对端已下线\n"); epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd); continue; } printf("收到消息:%s\n", rbuf);
strcat(rbuf, " :)"); if (send(fd, rbuf, strlen(rbuf), 0) == -1) { perror("send error"); return -1; } printf("发送成功\n"); } } }
close(sfd); close(epfd); return 0; }
|
epoll比另外两个效率高,肯定也更常用,但是跨主机时还是用select(我目前认知是这样的),接下来epoll还有两种工作模式:水平模式和边沿模式
epoll的两种工作模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 1、水平模式: 简称LT模式(level trigered),是默认的一种初始模式,并且该模式支持阻塞和非阻塞的形式 在这种模式下,当文件描述符准备就绪后,内核会通知使用者哪些文件描述符就绪了。 使用者可以对就绪的文件描述符进行操作。 如果使用者不做任何操作或者没有全部处理完该文件描述符的信息,内核会继续通知使用者处理数据 特点: 对于读事件:如果该文件描述符中的缓冲区中的数据没有被读取完毕,则内核会继续解除阻塞,让用户继续处理,直到缓冲区中没有数据可以处理为止。 后面的解除阻塞,是自动完成的,无需用户进行对文件描述符的后续操作。 对于写事件:检测文件描述符缓冲区是否可以,如果可用则解除阻塞,一般写文件描述符的缓冲区都是可以的,一般不对写文件描述符进行检测 2、边沿模式 简称ET模式(edge trigered),需要手动设置该模式,并且该模式一般支持非阻塞形式 在种模式下,当文件描述符准备就绪后,内核会通知使用者哪些文件描述符就绪了,但是仅仅只通知一次 使用者可以对就绪的文件描述符进行操作 如果使用者不做任何操作,或者没有全部处理该文件描述符的信息,内核不会通知使用者再次处理数据,直到下一次该文件描述符的事件产生 特点:对于读事件:如果文件描述符中的缓冲区数据没有读取完毕,则内核也不会再次解除阻塞,直到下一次的该文件描述符事件产生,但是下一次的文件描述符的事件,读取的是上一次没有读取完毕的内容 对于写事件:检测文件描述符缓冲区是否可以,如果可用则解除阻塞,一般写文件描述符的缓冲区都是可以的,一般不对写文件描述符进行检测 边沿触发模式的处理效率会更高一些,要求用户必须一次性处理文件描述符中的数据 3、如何设置边沿触发:在将文件描述符放入到epoll树中时,需要加一个属性 struct epoll_event ev; ev.event = EPOLLIN|EPOLLET;
|
上面字太多了,举个例子来说
水平模式就是这样的例子:接收用的字符数组容量仅有5(接收4个字符),但是客户端发来了15个字符,那么本次只能输出4个字符,下次该事件也会自动被检测到,直到15个字符输出完了
1 2 3 4 5 6 7 8
| 客户端: hellohellohellohello 服务器端: hell ohel lohe lloh ello
|
边沿模式就是:接收用的字符数组容量仅有5(接收4个字符),但是客户端发来了15个字符,那么本次只能输出4个字符,下次该事件不会被自动检测到,除非客户端再次发送消息了,但是服务器接收到的消息还是上次15个字符没接收完的部分
1 2 3 4 5 6
| 客户端: hello12345 abcde 服务器端: hell o123
|
三、广播和组播
一、网络属性
- 网络套接字在不同层中,有不同的设置,分别有应用层(套接字层)、传输层、网络层、以太网层
- 对套接字属性进行设置,可以使用setsockopt和getsockopt函数完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <sys/types.h> #include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen); int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen); 功能:设置或者获取套接字文件描述符的属性 参数1:套接字文件描述符 参数2:表示操作的套接字层 SOL_SOCKET:表示应用层或者套接字层,通过man 7 socket进行查找 该层中常用的属性:SO_REUSEADDR 地址快速重用 SO_BROADCAST 允许广播 SO_RCVTIMEO and SO_SNDTIMEO:发送或接收超时时间 IPPROTO_TCP:表示传输层的基于TCP的协议 IPPROTO_UDP:表示传输层中基于UDP的协议 IPPROTO_IP:表示网络层 该层常用的属性:IP_ADD_MEMBERSHIP,加入多播组 参数3:对应层中属性的名称 参数4:参数3属性的值,一般为int类型,其他类型,会给出 参数5:参数4的大小 返回值:成功返回0,失败返回-1并置位错误码
|
网络属性实例
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
| #include <myhead.h>
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_STREAM, 0); if(sfd == -1) { perror("socket error"); return -1; }
int reuse = -1; socklen_t reuselen; if(getsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, &reuselen) == -1) { perror("getsocketopt error"); return -1; } printf("设置前:%d\n", reuse);
reuse = 1; if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) { perror("setsockopt error"); return -1; } if(getsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, &reuselen) == -1) { perror("getsocketopt error"); return -1; } printf("设置后:%d\n", reuse); return 0; }
|
二、广播
- 像名称一样,是一对多的通信方式
- 在当前网络下的所有主机都会收到该消息,无论是否愿意收到消息
- 基于无连接的通信方式,UDP通信
- 发送端要设置网络属性为允许广播(应用层),并向广播地址发送消息
- 广播地址:网络号 +
全为1的主机号(也就是十进制的255),可以通过ifconfig查看
流程
1 2 3 4 5 6 7 8 9 10 11 12
| 服务器端: 1.socket创建用于发送数据的套接字 2.setsockopt设置套接字属性,允许广播setsockopt(sfd, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val)); 3.定义套接字地址信息结构体为广播 4.sendto数据发送到广播 5.close关闭套接字 客户端: 1.socket创建用于发送数据的套接字 2.bind绑定ip和端口号 3.定义套接字地址信息结构体 4.recvfrom数据接收 close关闭套接字
|
发送端:
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
| #include <myhead.h>
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1) { perror("socket error"); return -1; }
int broad = 1; if(setsockopt(sfd, SOL_SOCKET, SO_BROADCAST, &broad, sizeof(broad)) == -1) { perror("setsockopt error"); return -1; } printf("成功设置广播\n");
struct sockaddr_in rin; rin.sin_family = AF_INET; rin.sin_port = htons(8888); rin.sin_addr.s_addr = inet_addr("192.168.111.255"); char wbuf[128] = ""; while(1) { fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = '\0';
sendto(sfd, wbuf, strlen(wbuf), 0, (struct sockaddr*)&rin, sizeof(rin)); printf("发送成功\n");
} close(sfd); return 0; }
|
接收端:
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 47 48 49 50 51
| #include <myhead.h>
int main(int argc, const char *argv[]) { int rfd = socket(AF_INET, SOCK_DGRAM, 0); if(rfd == -1) { perror("socket error"); return -1; }
int broad = 1; if(setsockopt(rfd, SOL_SOCKET, SO_BROADCAST, &broad, sizeof(broad)) == -1) { perror("setsockopt error"); return -1; } printf("成功设置广播\n");
struct sockaddr_in rin; rin.sin_family = AF_INET; rin.sin_port = htons(8888); rin.sin_addr.s_addr = INADDR_ANY; if(bind(rfd, (struct sockaddr*)&rin, sizeof(rin)) == -1) { perror("bind error"); return -1; } printf("bind success\n"); char rbuf[128] = ""; struct sockaddr_in cin; socklen_t socklen = sizeof(cin); while(1) { bzero(rbuf, sizeof(rbuf));
recvfrom(rfd, rbuf, sizeof(rbuf), 0, (struct sockaddr*)&cin, &socklen); printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf);
} close(rfd); return 0; }
|
三、组播
- 组播相较于广播,主机数量少了,就相当于一个村和一家人
- 广播是给同一个网络下的所有主机发送消息,会占用大量的网络带宽,影响正常网络通信,造成网络拥塞
- 组播是给同一个多播组的成员发送消息
- 组播也是基于UDP实现的,发送者发送的消息,无论接收者愿不愿意,都会收到消息
- 组播地址:D类网络地址 (224.0.0.0 — 239.255.255.255)
流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 发送端: 1.socket创建用于消息通信的套接字文件描述符 2.bind绑定(非必须) 3.填充要发送的地址信息结构体 4.sendto发送数据到组播 5.close关闭套接字 接收端: 1.socket创建用于通信的套接字文件描述符 2.setsockopt设置套接字加入多播组 3.bind绑定地址信息 4.recvfrom接收数据 5.close关闭套接字 设置加入多播组: setsockopt(rfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)) imr是一个结构体类型的变量,如下 struct ip_mreqn { struct in_addr imr_multiaddr; struct in_addr imr_address; int imr_ifindex; };
|
发送端:
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
| #include <myhead.h>
int main(int argc, const char *argv[]) { int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1) { perror("socket error"); return -1; }
struct sockaddr_in rin; rin.sin_family = AF_INET; rin.sin_port = htons(8888); rin.sin_addr.s_addr = inet_addr("224.1.2.3"); char wbuf[128] = ""; struct sockaddr_in cin; socklen_t socklen = sizeof(cin); while(1) { bzero(wbuf, sizeof(wbuf));
fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = '\0';
sendto(sfd, wbuf, sizeof(wbuf), 0, (struct sockaddr*)&rin, sizeof(rin)); } close(sfd);
return 0; }
|
接收端:
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 47 48 49 50 51 52 53 54 55 56
| #include <myhead.h>
int main(int argc, const char *argv[]) { int rfd = socket(AF_INET, SOCK_DGRAM, 0); if(rfd == -1) { perror("socket error"); return -1; }
struct ip_mreqn imr; imr.imr_address.s_addr = inet_addr("224.1.2.3"); imr.imr_ifindex = 2; imr.imr_multiaddr.s_addr = inet_addr("192.168.111.122");
if(setsockopt(rfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)) == -1) { perror("setsockopt error"); return -1; } printf("成功加入多播组\n");
struct sockaddr_in rin; rin.sin_addr.s_addr = inet_addr("224.1.2.3"); rin.sin_family = AF_INET; rin.sin_port = htons(8888);
if(bind(rfd, (struct sockaddr*)&rin, sizeof(rin)) == -1) { perror("bind error"); return -1; } char rbuf[128] = ""; struct sockaddr_in cin; socklen_t socklen = sizeof(cin); while(1) { bzero(rbuf, sizeof(rbuf));
fgets(rbuf, sizeof(rbuf), stdin); rbuf[strlen(rbuf) - 1] = '\0';
recvfrom(rfd, rbuf, sizeof(rbuf), 0, (struct sockaddr*)&cin, &socklen); printf("[读取的消息为]:%s\n", rbuf); } close(rfd);
return 0; }
|
四、域套接字
一、域套接字是什么
- 域套接字就像IPC一样,实现同主机下多个进程之间的通信
- 不需要ip和端口号
- 通过内核空间进行数据交换
- 分为流式域套接字(基于TCP)和报式域套接字(基于UDP)
1 2 3 4 5 6 7 8 9 10
| 与网络套接字相关api的一点变化 int socket(int domain, int type, int protocol); 第一个参数不是AF_INET了,而是AF_UNIX int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 第二个参数对应的结构体变了: struct sockaddr_un { sa_family_t sun_family; char sun_path[UNIX_PATH_MAX]; 要求套 接字文件不存在 };
|
相关api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <unistd.h>
int access(const char *pathname, int mode); 功能:判断给定的文件是否具有给的的权限 参数1:要被判断的文件路径 参数2:要被判断的权限 R_OK:读权限 W_OK:写权限 X_OK:执行权限 F_OK:是否存在 返回值:如果要被判断的权限都存在,则返回0,否则返回-1并置位错误码 int unlink(const char *path); 功能:删除指定的文件 参数:要删除的文件路径 返回值:成功删除返回0,失败返回-1并置位错误码
|
二、流式域套接字
服务器端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| #include <myhead.h>
int main(int argc, const char *argv[]) { int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sfd == -1) { perror("socket error"); return -1; } printf("socket success sfd = %d\n", sfd);
if(access("./unix", F_OK) == 0) { if(unlink("./unix") == -1) { perror("unlink error"); return -1; } }
struct sockaddr_un sun; sun.sun_family = AF_UNIX; strcpy(sun.sun_path, "./unix");
if(bind(sfd, (struct sockaddr*)&sun, sizeof(sun)) == -1) { perror("bind error"); return -1; } printf("bind success\n"); if(listen(sfd, 128) == -1) { perror("listen error"); return -1; } printf("listen success\n");
struct sockaddr_un cin; socklen_t socklen = sizeof(cin);
int newfd = accept(sfd, (struct sockaddr*)&cin, &socklen); if(newfd == -1) { perror("accept error"); return -1; } printf("accept success sun.sun_path : %s\n", sun.sun_path); char rbuf[128] = ""; while(1) { bzero(rbuf, sizeof(rbuf));
int res = recv(newfd, rbuf, sizeof(rbuf), 0); if(res == 0) { printf("对端已经下线\n"); break; } printf("[%s]:%s\n", cin.sun_path, rbuf);
strcat(rbuf, " :)");
if(send(newfd, rbuf, strlen(rbuf), 0) == -1) { perror("send error"); return -1; } printf("发送成功\n"); }
close(newfd); close(sfd); return 0; }
|
客户端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #include <myhead.h>
int main(int argc, const char *argv[]) { int cfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(cfd == -1) { perror("socket error"); return -1; } printf("socket success cfd = %d\n", cfd);
if(access("./unix", F_OK) == 0) { if(unlink("./unix") == -1) { perror("unlink error"); return -1; } }
struct sockaddr_un sun; sun.sun_family = AF_UNIX; strcpy(sun.sun_path, "./unix");
if(bind(cfd, (struct sockaddr*)&sun, sizeof(sun)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
char wbuf[128] = ""; while(1) { bzero(wbuf, sizeof(wbuf));
fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = '\0';
if(send(cfd, wbuf, strlen(wbuf), 0) == -1) { perror("send error"); return -1; }
if(recv(cfd, wbuf, sizeof(wbuf) - 1, 0) == 0) { printf("对端已下线"); break; } printf("收到消息:%s\n", wbuf); }
close(cfd); return 0; }
|
三、报式域套接字
服务器端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include <myhead.h>
int main(int argc, const char *argv[]) { // 1、创建用于通信的套接字文件描述符 int sfd = socket(AF_UNIX, SOCK_DGRAM, 0); // SOCK_DGRAM表示基于udp if(sfd == -1) { perror("socket error"); return -1; } printf("socket success sfd = %d\n", sfd);
// 创建套接字文件 if(access("./unix", F_OK) == 0) { if(unlink("./unix") == -1) { perror("unlink error"); return -1; } } // 2、绑定ip地址和端口号 // 2.1 填充需要绑定的ip地址和端口号 struct sockaddr_un sun; sun.sun_family = AF_UNIX; strcpy(sun.sun_path, "./unix");
// 2.2 绑定工作 // 参数1:要被绑定的套接字文件描述符 // 参数2:要被绑定的地址信息结构体,需要进行强制转换,防止警告 // 参数3:参数2的大小 if(bind(sfd, (struct sockaddr*)&sun, sizeof(sun)) == -1) { perror("bind error"); return -1; } printf("bind success\n");
// 3、数据收发 char rbuf[128] = ""; // 定义容器接收对端的地址信息结构体 struct sockaddr_un cun; socklen_t socklen = sizeof(cun);
while(1) { // 清空容器 bzero(rbuf, sizeof(rbuf));
// 从客户端中读取消息 if(recvfrom(sfd, rbuf, sizeof(rbuf), 0, (struct sockaddr*)&cun, &socklen) == -1) { perror("recvfrom error"); return -1; } // 处理一下 strcat(rbuf, " :)");
// 将数据发送给客户端 sendto(sfd, rbuf, strlen(rbuf), 0, (struct sockaddr*)&cun, sizeof(cun)); printf("发送成功\n");
}
// 4、关闭套接字 close(sfd); return 0; }
|
客户端:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <myhead.h> #define SER_PORT 8888 #define SER_IP "192.168.228.136" #define CLI_PORT 9999 #define CLI_IP "192.168.228.136"
int main(int argc, const char *argv[]) { int cfd = socket(AF_UNIX, SOCK_DGRAM, 0); if(cfd == -1) { perror("socket error"); return -1; } printf("socket success cfd = %d\n", cfd);
if(access("./unix", F_OK) == 0) { if(unlink("./unix") == -1) { perror("unlink error"); return -1; } }
struct sockaddr_un cun; cun.sun_family = AF_UNIX; strcpy(cun.sun_path, "./linux");
if(bind(cfd, (struct sockaddr*)&cun, sizeof(cun)) == -1) { perror("bind error"); return -1; } printf("bind success\n"); char wbuf[128] = ""; struct sockaddr_un sun; sun.sun_family = AF_UNIX; strcpy(sun.sun_path, "./unix");
while(1) { bzero(wbuf, sizeof(wbuf));
fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = '\0';
if(sendto(cfd, wbuf, strlen(wbuf), 0, (struct sockaddr*)&sun, sizeof(sun)) == -1) { perror("sendto error"); break; } if(recvfrom(cfd, wbuf, sizeof(wbuf) - 1, 0, NULL, NULL) == -1) { perror("recvfrom error"); break; } printf("服务器发来的消息为:%s\n", wbuf); }
close(cfd); return 0; }
|
五、总结
本篇所有服务器模型都是基于TCP和UDP的服务器端和客户端的实现,所以说
一、网络编程基础
中讲解的TCP和UDP的服务器端和客户端的实现要掌握好,才能实现后面的并发、广播和组播、域套接字
本篇给了很多样例代码(讲解也大部分在代码中了),并没有详细介绍api,主要是太多了,现在字数已经1w6了
到此,网络编程结束。