您的位置:首页 > 文旅 > 美景 > 网络编程(学习)2024.9.2

网络编程(学习)2024.9.2

2024/10/6 8:38:50 来源:https://blog.csdn.net/qq_60450758/article/details/141901618  浏览:    关键词:网络编程(学习)2024.9.2

目录

服务器模型

1.循环服务器

2.并发服务器

(1)多进程模型

多进程特点

多进程案例

(2)多线程模型

多线程特点

多线程案例

网络超时检测

超时检测的必要性

网络超时检测方法

1.自带超时参数的函数

(1)select设置超时

(2)poll设置超时

2.利用setsockopt属性设置

设置sockfd接收超时

设置端口重用

服务器模型

1.循环服务器

一次只有一个客户端可以连接,但是客户端退出以后,下一个客户端可以继续连接

2.并发服务器

同时一时刻可以连接多个客户端,常见:IO多路复用(select、poll、epoll)、多进程、多线程

(1)多进程模型

多进程特点

1.fork之前的代码被复制,但是不会重新执行一遍;fork之后的代码被复制,并且再被执行一遍。
2.fork之后两个进程相互独立,子进程拷贝了父进程的所有代码,但内存空间独立
3.fork之前打开文件,fork之后拿到的是同一个文件描述符,操作的是同一个文件指针

多进程案例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/select.h>
#include <signal.h>
#include <wait.h>
#define N 64
char buf[N];
pid_t pid;void father(int sig)
{waitpid(-1, NULL, WNOHANG); // 回收子进程资源
}int main(int argc, char const *argv[])
{if (argc != 2){printf("用法:<port>\n");return -1;}// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.bind绑定IP和Port端口号struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY;socklen_t addrlen = sizeof(saddr);if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){perror("bind失败");close(sockfd);return -1;}printf("bind成功\n");// 3.监听listen将主动套接字变为被动套接字if (listen(sockfd, 7) < 0){perror("lisren失败");close(sockfd);return -1;}printf("listen成功\n");while (1){int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);if (acceptfd < 0){perror("accept失败");return -1;}printf("acceptfd:%d\n", acceptfd);printf("客户端ip:%s\t 端口号:%d 已连接\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pid = fork();if (pid == 0){// close(sockfd);while (1){memset(buf, 0, N);int ret = recv(acceptfd, buf, N, 0);if (ret < 0){perror("recv失败\n");break;}else if (ret == 0){printf("客户端ip:%s退出\n", inet_ntoa(caddr.sin_addr));break;}else{printf("%s客户端: %s\n", inet_ntoa(caddr.sin_addr), buf);}}close(acceptfd);exit(0);}else{close(acceptfd);signal(SIGCHLD, father);}}return 0;
}

(2)多线程模型

多线程特点

每来一个连接创建一个线程,IO多路复用以外,应用最广泛的并发服务器模型

多线程案例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>#define N 64void *mythread(void *arg)
{pthread_detach(pthread_self());int fd = *((int *)arg);free(arg);char buf[N];while (1){memset(buf, 0, N);int ret = recv(fd, buf, N, 0);if (ret < 0){perror("recv失败");break;}else if (ret == 0){printf("客户端acceptfd:%d 退出\n", fd);close(fd);break;}else{printf("客户端: %s\n", buf);}}pthread_exit(NULL);
}int main(int argc, char const *argv[])
{if (argc != 2){printf("用法:%s <port>\n", argv[0]);return -1;}int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket失败");return -1;}printf("sockfd:%d\n", sockfd);struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY;socklen_t addrlen = sizeof(saddr);if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){perror("bind失败");close(sockfd);return -1;}printf("bind成功\n");if (listen(sockfd, 7) < 0){perror("listen失败");close(sockfd);return -1;}printf("listen成功\n");while (1){int *acceptfd = malloc(sizeof(int)); // 为每个客户端连接分配内存if (acceptfd == NULL){perror("内存分配失败");close(sockfd);return -1;}*acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);if (*acceptfd < 0){perror("accept失败");free(acceptfd);continue;}printf("acceptfd: %d\n", *acceptfd);printf("客户端ip:%s 端口号:%d 连接\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pthread_t tid;if (pthread_create(&tid, NULL, mythread, acceptfd) != 0){perror("创建线程失败");close(*acceptfd);free(acceptfd);continue;}}close(sockfd);return 0;
}

网络超时检测

在网络通信中,很多操作会使得进程阻塞:
TCP套接字中的recv/accept
UDP套接字中的recvfrom

超时检测的必要性

○避免进程在没有数据时无限制地阻塞
○实现某些特定协议要求,比如某些设备规定,发送请求数据后,如果多长时间后没有收到来自设备的回复,需要做出一些特殊处理

网络超时检测方法

1.自带超时参数的函数

例如使用select/poll/epoll函数最后一个参数可以设置超时。

(1)select设置超时

struct timeval tm = {2, 0};//设置2s打算阻塞
sret = select(maxfd + 1, &tempfds, NULL, NULL, &tm);
第五个参数:
struct timeval

{
        long    tv_sec;         /*秒*/
        long    tv_usec;        /*微秒*/
};
 注:select的超时检测不会更新,所以每次重新超时等待之前要更新等待时间

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/select.h>#define N 64
char buf[N];
#define ERR_MSG(msg)                           \do                                         \{                                          \fprintf(stderr, "line:%d ", __LINE__); \perror(msg);                           \} while (0)int main(int argc, char const *argv[])
{if (argc != 2){printf("用法:<port>\n");return -1;}// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.bind绑定IP和Port端口号struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));// saddr.sin_addr.s_addr = inet_addr("192.168.50.213");socklen_t addrlen = sizeof(saddr);
#if 0saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
#elsesaddr.sin_addr.s_addr = INADDR_ANY;
#endifif (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){ERR_MSG("bind失败");close(sockfd);return -1;}printf("bind成功\n");// 3.监听listen将主动套接字变为被动套接字if (listen(sockfd, 7) < 0){ERR_MSG("lisren失败");close(sockfd);return -1;}printf("listen成功\n");// 第一步:建表初始化fd_set readfds, tempfds;FD_ZERO(&readfds);FD_SET(sockfd, &readfds);FD_SET(0, &readfds);int max = sockfd;while (1){struct timeval tm = {2, 0};memset(buf, 0, N);tempfds = readfds;int ret = select(max + 1, &tempfds, NULL, NULL, &tm);if (ret == 0){printf("time out\n");}if (FD_ISSET(sockfd, &tempfds)){// 4.accept阻塞等待链接int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);if (acceptfd < 0){ERR_MSG("accept失败\n");return -1;}printf("acceptfd:%d\n", acceptfd);printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));FD_SET(acceptfd, &readfds);if (max < acceptfd){max = acceptfd;}}// 5.发送else if (FD_ISSET(0, &tempfds)){scanf("%s", buf);for (int i = 4; i <= max; i++){if (FD_ISSET(i, &readfds)){send(i, buf, N, 0);}}}// 6.接收for (int n = 4; n <= max; n++){if (FD_ISSET(n, &tempfds)){int ret = recv(n, buf, N, 0);if (ret < 0){perror("recv失败");close(sockfd);return -1;}else if (ret > 0){printf("客户端acceptfd%d:%s\n", n, buf);}else{printf("客户端acceptfd:%d退出\n", n);FD_CLR(n, &readfds);close(n);while (!FD_ISSET(max, &readfds)){max--;}}}}}close(sockfd);return 0;
}

(2)poll设置超时

直接在poll(fds, last + 1, 2000);设置,延迟2s;

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>#define N 64
char buf[N];
#define ERR_MSG(msg)                           \do                                         \{                                          \fprintf(stderr, "line:%d ", __LINE__); \perror(msg);                           \} while (0)int main(int argc, char const *argv[])
{if (argc != 2){printf("用法:<port>\n");return -1;}// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.bind绑定IP和Port端口号struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = inet_addr("192.168.50.213");socklen_t addrlen = sizeof(saddr);// #if 0//     saddr.sin_addr.s_addr = inet_addr("0.0.0.0");// #else//     saddr.sin_addr.s_addr = INADDR_ANY;// #endifif (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){ERR_MSG("bind失败");close(sockfd);return -1;}printf("bind成功\n");// 3.监听listen将主动套接字变为被动套接字if (listen(sockfd, 7) < 0){ERR_MSG("lisren失败");close(sockfd);return -1;}printf("listen成功\n");// 第一步:建表初始化struct pollfd fds[100];int last = -1;// 第二步:填表fds[++last].fd = 0;fds[last].events = POLLIN;fds[last].revents = 0;fds[++last].fd = sockfd;fds[last].events = POLLIN;fds[last].revents = 0;while (1){memset(buf, 0, N);int po = poll(fds, last + 1, 2000);if (po == -1){perror("select失败");close(sockfd);return -1;}for (int i = 0; i <= last; i++){if (fds[i].revents == POLLIN){if (fds[i].fd == sockfd){// 4.accept阻塞等待链接int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);if (acceptfd < 0){ERR_MSG("accept失败\n");return -1;}printf("acceptfd:%d\n", acceptfd);printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));fds[++last].fd = acceptfd;fds[last].events = POLLIN;fds[last].revents = 0;}else if (fds[i].fd == 0){scanf("%s", buf);for (int j = 2; j <= last; j++){send(fds[j].fd, buf, N, 0);}}else{int ret = recv(fds[i].fd, buf, N, 0);if (ret < 0){perror("recv失败");close(sockfd);return -1;}else if (ret > 0){printf("客户端%s:%s\n", inet_ntoa(caddr.sin_addr), buf);}else{printf("客户端acceptfd:%d退出\n", fds[i].fd);close(fds[i].fd);fds[i] = fds[last];last--;}}}}}close(sockfd);return 0;
}

2.利用setsockopt属性设置

Linux中socket属性

API接口
功能:设置/获取网络属性;
#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);
参数:
        int sockfd:指定要设置/获取哪个套接字的属性;
        int level:指定要控制的协议层次;
                SOL_SOCKET:应用层 通用套接字选项;  man 7 socket
                IPPROTO_TCP:TCP选项               man 7 TCP
                IPPROTO_UDP:UDP选项               man 7 UDP
                IPPROTO_IP:IP选项;                man 7 IP
        int optname:指定要控制的内容,指定控制方式;
                 --- SOL_SOCKET: man 7 socket -----
                SO_REUSEADDR:允许端口快速重用        optval: int*
                SO_BROADCAST:允许广播        optval: int*   
                SO_RCVBUF/SO_SNDBUF:接收缓冲区 发送缓冲区大小
                SO_RCVTIMEO/SO_SNDTIMEO:接收超时时间,发送超时时间

        void *optval:根据optname不同,该类型不同;
        socklen_t optlen/socklen_t *optlen:真实的optval指针指向的内存空间的大小;
返回值:
        成功:返回0
        失败:返回-1

设置sockfd接收超时

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>#define ERR_MSG(msg)                           \do                                         \{                                          \fprintf(stderr, "line:%d ", __LINE__); \perror(msg);                           \} while (0)int main(int argc, char const *argv[])
{if (argc != 2){printf("用法:<port>\n");return -1;}// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.bind绑定IP和Port端口号struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));// saddr.sin_addr.s_addr = inet_addr("192.168.50.213");socklen_t addrlen = sizeof(saddr);
#if 0saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
#elsesaddr.sin_addr.s_addr = INADDR_ANY;
#endifif (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){perror("bind失败");return -1;}printf("bind成功\n");// 3.监听listen将主动套接字变为被动套接字if (listen(sockfd, 7) < 0){ERR_MSG("lisren失败");return -1;}printf("listen成功\n");struct timeval val = {2, 0};setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &val, sizeof(val));while (1){// 4.accept阻塞等待链接int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);if (acceptfd < 0){if (errno == 11){char a[10];printf("accept超时,是否继续,继续请输入yes,退出请输入no\n");scanf(" %s", a);if (strcmp(a, "yes") == 0){continue;}if (strcmp(a, "no") == 0){break;}else{printf("输入错误,请重新输入\n");continue;}}perror("accept失败");return -1;}printf("acceptfd:%d\n", acceptfd);printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));// 5.发送
#define N 64char buf[N];while (1){memset(buf, 0, N);int ret = recv(acceptfd, buf, N, 0); // MSG_DONTWAITif (ret < 0){if (errno == 11){printf("读缓存区内没数据\n");continue;}else{perror("recv失败\n");return -1;}}else if (ret == 0){printf("客户端ip:%s退出\n", inet_ntoa(caddr.sin_addr));close(acceptfd);break;}else{printf("客户端%s\n", buf);}}}close(sockfd);return 0;
}

设置端口重用

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>#define ERR_MSG(msg)                           \do                                         \{                                          \fprintf(stderr, "line:%d ", __LINE__); \perror(msg);                           \} while (0)int main(int argc, char const *argv[])
{if (argc != 2){printf("用法:<port>\n");return -1;}// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.bind绑定IP和Port端口号struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));// saddr.sin_addr.s_addr = inet_addr("192.168.50.213");socklen_t addrlen = sizeof(saddr);
#if 0saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
#elsesaddr.sin_addr.s_addr = INADDR_ANY;
#endifint flag;socklen_t len = sizeof(flag);getsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, &len);flag = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, len);printf("flag:%d\n", flag);if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){perror("bind失败");return -1;}printf("bind成功\n");// 3.监听listen将主动套接字变为被动套接字if (listen(sockfd, 7) < 0){ERR_MSG("lisren失败");return -1;}printf("listen成功\n");while (1){// 4.accept阻塞等待链接int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);if (acceptfd < 0){perror("accept失败");return -1;}printf("acceptfd:%d\n", acceptfd);printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));// 5.发送
#define N 64char buf[N];while (1){memset(buf, 0, N);int ret = recv(acceptfd, buf, N, 0); // MSG_DONTWAITif (ret < 0){if (errno == 11){printf("读缓存区内没数据\n");continue;}else{perror("recv失败\n");return -1;}}else if (ret == 0){printf("客户端ip:%s退出\n", inet_ntoa(caddr.sin_addr));close(acceptfd);break;}else{printf("客户端%s\n", buf);}}}close(sockfd);return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com