交流
商城
MCN
登入
注册
首页
提问
分享
讨论
建议
公告
动态
发表新帖
发表新帖
Redis 2-1、c语言实现tcp连接
分享
未结
0
958
李延
LV6
2021-07-24
悬赏:20积分
# 阻塞连接 参考:[https://blog.csdn.net/qq_41725312/article/details/90375742](https://blog.csdn.net/qq_41725312/article/details/90375742) ## 代码 ### 服务器 ```c /** * * 服务端 * @return */ int server_socket() //创建套接字和初始化以及监听函数 { //af ,地址族(Address Family),常用AF_INET(IPv4) 和 AF_INET6(IPv6)。 //type ,数据传输方式,常用的有 SOCK_STREAM(面向连接)和 SOCK_DGRAM(无连接) //protocol 表示传输协议,常用的有 IPPROTO_TCP(TCP协议) 和 IPPTOTO_UDP(UDP协议) int listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建一个负责监听的套接字 if (listen_socket == -1) { perror("socket"); return -1; } //struct sockaddr_in{ // sa_family_t sin_family; //地址族(Address Family),也就是地址类型 // uint16_t sin_port; //16位的端口号 // struct in_addr sin_addr; //32位IP地址 // char sin_zero[8]; //不使用,一般用0填充 //}; struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));//每个字节都用0填充 addr.sin_family = AF_INET; /* Internet地址族 使用IPv4地址*/ addr.sin_port = htons(PORT); /* 端口号 */ addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ //将套接字与特定的IP地址和端口绑定起来 int ret = bind(listen_socket, (struct sockaddr *) &addr, sizeof(addr)); //连接 if (ret == -1) { perror("bind"); return -1; } //sock 需要进入监听状态的套接字 //backlog 请求队列的最大长度 //套接字进入被动监听状态 ret = listen(listen_socket, 20); //监听 if (ret == -1) { perror("listen"); return -1; } struct sockaddr_in cliaddr; int addrlen = sizeof(cliaddr); printf("等待客户端连接。。。。\n"); //套接字进入被动监听状态 //sock 为服务器端套接字 //addr 为 sockaddr_in 结构体变量 //addrlen 为参数 addr 的长度,可由 sizeof() 求得 int client_socket = accept(listen_socket, (struct sockaddr *) &cliaddr, &addrlen); if (client_socket == -1) { perror("accept"); return -1; } printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr)); //接受请求 char buf[SIZE]; while (1) { //读取数据 //fd 为要写入的文件的描述符 //buf 为要写入的数据的缓冲区地址 //nbytes 为要写入的数据的字节数。 int ret = read(client_socket, buf, SIZE - 1); if (ret == -1) { perror("read"); break; } if (ret == 0) { break; } buf[ret] = '\0'; int i; for (i = 0; i < ret; i++) { buf[i] = buf[i] + 'A' - 'a'; } printf("%s\n", buf); write(client_socket, buf, ret); if (strncmp(buf, "end", 3) == 0) { break; } } close(client_socket); return 0; } ``` ### 客户端 ```c #include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll int main(){ //初始化DLL WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); //创建套接字 SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //向服务器发起请求 sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充 sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); sockAddr.sin_port = htons(1234); connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //接收服务器传回的数据 char szBuffer[MAXBYTE] = {0}; recv(sock, szBuffer, MAXBYTE, NULL); //输出接收到的数据 printf("Message form server: %s\n", szBuffer); //关闭套接字 closesocket(sock); //终止使用 DLL WSACleanup(); system("pause"); return 0; } ``` ## 优缺点 我们看到客户端在等待服务端请求时,使用的是accept,而这个方法是阻塞的。此时当前线程无法做其他事情。 # kqueue IO复用 kueue是在UNIX上比较高效的IO复用技术。 所谓的IO复用,就是同时等待多个文件描述符就绪,以系统调用的形式提供。如果所有文件描述符都没有就绪的话,该系统调用阻塞,否则调用返回,允许用户进行后续的操作。 在redis中的网络也采用这种方式 参考:[https://www.cnblogs.com/luminocean/archive/2016/06/30/5631336.html](https://www.cnblogs.com/luminocean/archive/2016/06/30/5631336.html) ```c int test() { const static int FD_NUM = 2;// 要监视多少个文件描述符 int kq = kqueue(); // kqueue对象 // kqueue的事件结构体,不需要直接操作 struct kevent changes[FD_NUM]; // 要监视的事件列表 struct kevent events[FD_NUM]; // kevent返回的事件列表(参考后面的kevent函数) int stdin_fd = STDIN_FILENO; int stdout_fd = STDOUT_FILENO; // 在changes列表中注册标准输入流的读事件 以及 标准输出流的写事件 // 最后一个参数可以是任意的附加数据(void * 类型),在这里给事件附上了当前的文件描述符,后面会用到 EV_SET(&changes[0], stdin_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, &stdin_fd); EV_SET(&changes[1], stdout_fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &stdin_fd); // 进行kevent函数调用,如果changes列表里有任何就绪的fd,则把该事件对应的结构体放进events列表里面 // 返回值是这次调用得到了几个就绪的事件 (nev = number of events) int nev = kevent(kq, changes, FD_NUM, events, FD_NUM, NULL); // 已经就绪的文件描述符数量 for (int i = 0; i < nev; i++) { struct kevent event = events[i]; // 一个个取出已经就绪的事件 int ready_fd = *((int *) event.udata); // 从附加数据里面取回文件描述符的值 if (ready_fd == stdin_fd) { // 读取ready_fd } else if (ready_fd == stdin_fd) { // 写入ready_fd } } } ```
回帖
消灭零回复
提交回复
热议榜
java 相关知识分享
8
好的程序员与不好的程序员
6
写给工程师的十条精进原则
5
spring boot以jar包运行配置的logback日志文件没生成
5
一步一步分析SpringBoot启动源码(一)
5
MockMvc测试
5
【吐槽向】是不是有个吐槽的板块比较好玩
4
logstash jdbc同步mysql多表数据到elasticsearch
3
IntelliJ IDEA 优质License Server
3
.gitignore忽略规则
3
SpringBoot启动源码分析
3
一步一步分析SpringBoot启动源码(三)
3
2
一步一步分析SpringBoot启动源码(二)
2
积分不够将无法发表新帖
2
官方产品
Meta-Boot - 基于MCN
MCN - 快速构建SpringBoot应用
微信扫码关注公众号