赞
踩
在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址;
思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上, 但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析;
端口号(port)是传输层协议的内容;
pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?
另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要 发给谁";
此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一 些细节问题:
此处也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识:
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
- 将0x1234abcd写入到以0x0000开始的内存中,则结果为:
- big-endian little-endian
- 0x0000 0x12 0xcd
- 0x0001 0x23 0xab
- 0x0002 0xab 0x34
- 0x0003 0xcd 0x12
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换;
- #include<arpa/inet.h>
-
- uint32_t htonl(uint32_t hostlong);
- uint16_t htons(uint16_t hostshort);
- uint32_t ntohl(uint32_t netlong);
- uint16_t ntohs(uint16_t netshort);
- // 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
- int socket(int domain, int type, int protocol);
-
- // 绑定端口号 (TCP/UDP, 服务器)
- int bind(int socket, const struct sockaddr *address,
- socklen_t address_len);
-
- // 开始监听socket (TCP, 服务器)
- int listen(int socket, int backlog);
-
- // 接收请求 (TCP, 服务器)
- int accept(int socket, struct sockaddr* address,
- socklen_t* address_len);
-
- // 建立连接 (TCP, 客户端)
- int connect(int sockfd, const struct sockaddr *addr,
- socklen_t addrlen);
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同;
sockaddr 结构
sockaddr_in 结构
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主 要有三部分信息: 地址类型, 端口号, IP地址.;
- typedef uint32_t in_addr_t;
- struct in_addr
- {
- int_addr_t s_addr;
- };
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
实现一个简单的英译汉的功能
备注: 代码中会用到 地址转换函数 . 参考接下来的章节;
udp_socket.hpp
- #pragma once
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <cassert>
- #include <string>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- typedef struct sockaddr sockaddr;
- typedef struct sockaddr_in sockaddr_in;
- class UdpSocket {
- public:
- UdpSocket() : fd_(-1)
- {}
-
- bool Socket()
- {
- fd_ = socket(AF_INET, SOCK_DGRAM, 0);
- if (fd_ < 0)
- {
- perror("socket");
- return false;
- }
- return true;
- }
-
- bool Close()
- {
- close(fd_);
- return true;
- }
-
- bool Bind(const std::string& ip, uint16_t port)
- {
- sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = inet_addr(ip.c_str());
- addr.sin_port = htons(port);
- int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
- if (ret < 0)
- {
- perror("bind");
- return false;
- }
- return true;
- }
-
- bool RecvFrom(std::string* buf, std::string* ip = NULL, uint16_t* port = NULL)
- {
- char tmp[1024 * 10] = {0};
- sockaddr_in peer;
- socklen_t len = sizeof(peer);
- ssize_t read_size = recvfrom(fd_, tmp,
- sizeof(tmp) - 1, 0, (sockaddr*)&peer, &len);
- if (read_size < 0)
- {
- perror("recvfrom");
- return false;
- }
- // 将读到的缓冲区内容放到输出参数中
- buf->assign(tmp, read_size);
- if (ip != NULL)
- {
- *ip = inet_ntoa(peer.sin_addr);
- }
- if (port != NULL)
- {
- *port = ntohs(peer.sin_port);
- }
-
- return true;
- }
-
- bool SendTo(const std::string& buf, const std::string& ip, uint16_t port)
- {
- sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = inet_addr(ip.c_str());
- addr.sin_port = htons(port);
- ssize_t write_size = sendto(fd_, buf.data(), buf.size(), 0, (sockaddr*)&addr,/
- sizeof(addr));
-
- if (write_size < 0)
- {
- perror("sendto");
- return false;
- }
- return true;
- }
- private:
- int fd_;
- };
udp_server.hpp
- #pragma once
- #include "udp_socket.hpp"
- // C 式写法
- // typedef void (*Handler)(const std::string& req, std::string* resp);
- // C++ 11 式写法, 能够兼容函数指针, 仿函数, 和 lamda
-
- #include <functional>
- typedef std::function<void (const std::string&, std::string* resp)> Handler;
- class UdpServer {
- public:
- UdpServer()
- {
- assert(sock_.Socket());
- }
-
- ~UdpServer()
- {
- sock_.Close();
- }
-
- bool Start(const std::string& ip, uint16_t port, Handler handler)
- {
- // 1. 创建 socket
- // 2. 绑定端口号
- bool ret = sock_.Bind(ip, port);
- if (!ret)
- {
- return false;
- }
- // 3. 进入事件循环
- for (;;)
- {
- // 4. 尝试读取请求
- std::string req;
- std::string remote_ip;
- uint16_t remote_port = 0;
- bool ret = sock_.RecvFrom(&req, &remote_ip, &remote_port);
- if (!ret)
- {
- continue;
- }
- std::string resp;
- // 5. 根据请求计算响应
- handler(req, &resp);
- // 6. 返回响应给客户端
- sock_.SendTo(resp, remote_ip, remote_port);
- printf("[%s:%d] req: %s, resp: %s\n", remote_ip.c_str(), remote_port,
- req.c_str(), resp.c_str());
- }
- sock_.Close();
- return true;
- }
- private:
- UdpSocket sock_;
- };
以上代码是对 udp 服务器进行通用接口的封装. 基于以上封装, 实现一个查字典的服务器就很容易了;
dict_server.cc
- #include "udp_server.hpp"
- #include <unordered_map>
- #include <iostream>
-
- std::unordered_map<std::string, std::string> g_dict;
-
- void Translate(const std::string& req, std::string* resp)
- {
- auto it = g_dict.find(req);
- if (it == g_dict.end())
- {
- *resp = "未查到!";
- return;
- }
- *resp = it->second;
- }
-
- int main(int argc, char* argv[])
- {
- if (argc != 3)
- {
- printf("Usage ./dict_server [ip] [port]\n");
- return 1;
- }
- // 1. 数据初始化
- g_dict.insert(std::make_pair("hello", "你好"));
- g_dict.insert(std::make_pair("world", "世界"));
- g_dict.insert(std::make_pair("c++", "最好的编程语言"));
-
- // 2. 启动服务器
- UdpServer server;
- server.Start(argv[1], atoi(argv[2]), Translate);
- return 0;
- }
-
udp_client.hpp
- #pragma once
- #include "udp_socket.hpp"
- class UdpClient
- {
- public:
- UdpClient(const std::string& ip, uint16_t port) : ip_(ip), port_(port)
- {
- assert(sock_.Socket());
- }
-
- ~UdpClient()
- {
- sock_.Close();
- }
-
- bool RecvFrom(std::string* buf)
- {
- return sock_.RecvFrom(buf);
- }
-
- bool SendTo(const std::string& buf)
- {
- return sock_.SendTo(buf, ip_, port_);
- }
- private:
- UdpSocket sock_;
- // 服务器端的 IP 和 端口号
- std::string ip_;
- uint16_t port_;
- };
- #include "udp_client.hpp"
- #include <iostream>
- int main(int argc, char* argv[])
- {
- if (argc != 3)
- {
- printf("Usage ./dict_client [ip] [port]\n");
- return 1;
- }
-
- UdpClient client(argv[1], atoi(argv[2]));
- for (;;)
- {
- std::string word;
- std::cout << "请输入您要查的单词: ";
- std::cin >> word;
- if (!std::cin)
- {
- std::cout << "Good Bye"<< std::endl;
- break;
- }
- client.SendTo(word);
- std::string result;
- client.RecvFrom(&result);
- std::cout << word << " 意思是 " << result << std::endl;
- }
- return 0;
- }
本节只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的 IP 地址但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;
字符串转in_addr的函数:
- #include<arpa/inet.h>
-
- int inet_aton(const char* strptr, struct in_addr *addrptr);
- int_addr_t inet_addr(const char* strptr);
- int inet_pton(int family, const char *strptr, void *addrptr);
in_addr转字符串的函数:
- char *inet_ntoa(struct in_addr inaddr);
- const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr;
代码示例:
- #include<stdio.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include<arpa/inet.h>
-
- int main()
- {
- strcut sockaddr_in addr;
- inet_aton("127.0.0.1", &addr.sin_addr);
- uint32_t *ptr = (uint32_t*)(&addr.sin_addr);
- printf("addr: %x\n", *ptr);
- printf("addr_str : %s\n", inet_ntoa(addr.sin_addr));
- return 0;
- }
inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是 否需要调用者手动释放呢?
man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放. 那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:
运行结果如下:
- [ketil@tz addr_convert]$ ./a.out
- ptr1: 255.255.255.255, ptr2: 255.255.255.255
因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果;
多线程调用inet_ntoa代码示例如下:
- #include <stdio.h>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <pthread.h>
- void* Func1(void* p)
- {
- struct sockaddr_in* addr = (struct sockaddr_in*)p;
- while (1)
- {
- char* ptr = inet_ntoa(addr->sin_addr);
- printf("addr1: %s\n", ptr);
- }
- return NULL;
- }
-
- void* Func2(void* p)
- {
- struct sockaddr_in* addr = (struct sockaddr_in*)p;
- while (1)
- {
- char* ptr = inet_ntoa(addr->sin_addr);
- printf("addr2: %s\n", ptr);
- }
- return NULL;
- }
-
- int main()
- {
- pthread_t tid1 = 0;
- struct sockaddr_in addr1;
- struct sockaddr_in addr2;
- addr1.sin_addr.s_addr = 0;
- addr2.sin_addr.s_addr = 0xffffffff;
- pthread_create(&tid1, NULL, Func1, &addr1);
- pthread_t tid2 = 0;
- pthread_create(&tid2, NULL, Func2, &addr2);
- pthread_join(tid1, NULL);
- pthread_join(tid2, NULL);
- return 0;
- }
和刚才UDP类似. 实现一个简单的英译汉的功能
下面介绍程序中用到的socket API,这些函数都在sys/socket.h中;
我们的程序中对myaddr参数是这样初始化的:
- bzero(&servaddr, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servaddr.sin_port = htons(SERV_PORT);
服务器程序结构是这样的:
- while(1)
- {
- cliaddr_len = sizeof(cliaddr);
- connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
- n = read(connfd, buf, MAXLINE);
- ...
- close(connfd);
- }
tcp_socket.hpp
- #pragma once
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <string>
- #include <cassert>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <fcntl.h>
-
- typedef struct sockaddr sockaddr;
- typedef struct sockaddr_in sockaddr_in;
-
- #define CHECK_RET(exp) if (!(exp)) {\
- return false;\
- }
-
- class TcpSocket {
- public:
- TcpSocket() : fd_(-1) {}
-
- TcpSocket(int fd) : fd_(fd) {}
-
- bool Socket()
- {
- fd_ = socket(AF_INET, SOCK_STREAM, 0);
- if (fd_ < 0)
- {
- perror("socket");
- return false;
- }
- printf("open fd = %d\n", fd_);
- return true;
- }
-
- bool Close() const
- {
- close(fd_);
- printf("close fd = %d\n", fd_);
- return true;
- }
-
- bool Bind(const std::string& ip, uint16_t port) const
- {
- sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = inet_addr(ip.c_str());
- addr.sin_port = htons(port);
- int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
- if (ret < 0) {
- perror("bind");
- return false;
- }
- return true;
- }
-
- bool Listen(int num) const
- {
- int ret = listen(fd_, num);
- if (ret < 0)
- {
- perror("listen");
- return false;
- }
- return true;
- }
-
- bool Accept(TcpSocket* peer, std::string* ip = NULL, uint16_t* port = NULL) const
- {
- sockaddr_in peer_addr;
- socklen_t len = sizeof(peer_addr);
- int new_sock = accept(fd_, (sockaddr*)&peer_addr, &len);
- if (new_sock < 0)
- {
- perror("accept");
- return false;
- }
- printf("accept fd = %d\n", new_sock);
- peer->fd_ = new_sock;
- if (ip != NULL)
- {
- *ip = inet_ntoa(peer_addr.sin_addr);
- }
- if (port != NULL)
- {
- *port = ntohs(peer_addr.sin_port);
- }
- return true;
- }
-
- bool Recv(std::string* buf) const
- {
- buf->clear();
- char tmp[1024 * 10] = {0};
- // [注意!] 这里的读并不算很严谨, 因为一次 recv 并不能保证把所有的数据都全部读完
- // 参考 man 手册 MSG_WAITALL 节.
- ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
- if (read_size < 0)
- {
- perror("recv");
- return false;
- }
- if (read_size == 0)
- {
- return false;
- }
- buf->assign(tmp, read_size);
- return true;
- }
-
- bool Send(const std::string& buf) const
- {
- ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
- if (write_size < 0)
- {
- perror("send");
- return false;
- }
- return true;
- }
-
- bool Connect(const std::string& ip, uint16_t port) const
- {
- sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = inet_addr(ip.c_str());
- addr.sin_port = htons(port);
- int ret = connect(fd_, (sockaddr*)&addr, sizeof(addr));
- if (ret < 0)
- {
- perror("connect");
- return false;
- }
- return true;
- }
-
- int GetFd() const
- {
- return fd_;
- }
- private:
- int fd_;
- };
tcp_server.hpp
- #pragma once
- #include <functional>
- #include "tcp_socket.hpp"
- typedef std::function<void (const std::string& req, std::string* resp)> Handler;
- class TcpServer
- {
- public:
- TcpServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port)
- {}
-
- bool Start(Handler handler)
- {
- // 1. 创建 socket;
- CHECK_RET(listen_sock_.Socket());
- // 2. 绑定端口号
- CHECK_RET(listen_sock_.Bind(ip_, port_));
- // 3. 进行监听
- CHECK_RET(listen_sock_.Listen(5));
- // 4. 进入事件循环
- for (;;)
- {
- // 5. 进行 accept
- TcpSocket new_sock;
- std::string ip;
- uint16_t port = 0;
- if (!listen_sock_.Accept(&new_sock, &ip, &port))
- {
- continue;
- }
- printf("[client %s:%d] connect!\n", ip.c_str(), port);
- // 6. 进行循环读写
- for (;;)
- {
- std::string req;
- // 7. 读取请求. 读取失败则结束循环
- bool ret = new_sock.Recv(&req);
- if (!ret)
- {
- printf("[client %s:%d] disconnect!\n", ip.c_str(), port);
- // [注意!] 需要关闭 socket
- new_sock.Close();
- break;
- }
- // 8. 计算响应
- std::string resp;
- handler(req, &resp);
- // 9. 写回响应
- new_sock.Send(resp);
- printf("[%s:%d] req: %s, resp: %s\n", ip.c_str(), port,
- req.c_str(), resp.c_str());
- }
- }
- return true;
- }
-
- private:
- TcpSocket listen_sock_;
- std::string ip_;
- uint64_t port_;
- };
- #include <unordered_map>
- #include "tcp_server.hpp"
- std::unordered_map<std::string, std::string> g_dict;
-
- void Translate(const std::string& req, std::string* resp)
- {
- auto it = g_dict.find(req);
- if (it == g_dict.end())
- {
- *resp = "未找到";
- return;
- }
- *resp = it->second;
- return;
- }
-
- int main(int argc, char* argv[])
- {
- if (argc != 3)
- {
- printf("Usage ./dict_server [ip] [port]\n");
- return 1;
- }
-
- // 1. 初始化词典
- g_dict.insert(std::make_pair("hello", "你好"));
- g_dict.insert(std::make_pair("world", "世界"));
- g_dict.insert(std::make_pair("bit", "贼NB"));
-
- // 2. 启动服务器
- TcpServer server(argv[1], atoi(argv[2]));
- server.Start(Translate);
- return 0;
- }
tcp_client.hpp
- #pragma once
- #include "tcp_socket.hpp"
- class TcpClient
- {
- public:
- TcpClient(const std::string& ip, uint16_t port) : ip_(ip), port_(port)
- {
- // [注意!!] 需要先创建好 socket
- sock_.Socket();
- }
-
- ~TcpClient()
- {
- sock_.Close();
- }
-
- bool Connect()
- {
- return sock_.Connect(ip_, port_);
- }
-
- bool Recv(std::string* buf)
- {
- return sock_.Recv(buf);
- }
-
- bool Send(const std::string& buf)
- {
- return sock_.Send(buf);
- }
- private:
- TcpSocket sock_;
- std::string ip_;
- uint16_t port_;
- };
dict_client.cc
- #include "tcp_client.hpp"
- #include <iostream>
- int main(int argc, char* argv[])
- {
- if (argc != 3)
- {
- printf("Usage ./dict_client [ip] [port]\n");
- return 1;
- }
- TcpClient client(argv[1], atoi(argv[2]));
- bool ret = client.Connect();
- if (!ret)
- {
- return 1;
- }
- for (;;)
- {
- std::cout << "请输入要查询的单词:" << std::endl;
- std::string word;
- std::cin >> word;
- if (!std::cin)
- {
- break;
- }
- client.Send(word);
- std::string result;
- client.Recv(&result);
- std::cout << result << std::endl;
- }
- return 0;
- }
由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配;
注意:
再启动一个客户端, 尝试连接服务器, 发现第二个客户端, 不能正确的和服务器进行通信;
分析原因, 是因为我们accecpt了一个请求之后, 就在一直while循环尝试read, 没有继续调用到accecpt, 导致不能接 受新的请求;
当前的这个TCP, 只能处理一个连接, 这是不科学的;
通过每个请求, 创建子进程的方式来支持多连接;
tcp_process_server.hpp
- #pragma once
- #include <functional>
- #include <signal.h>
- #include "tcp_socket.hpp"
- typedef std::function<void (const std::string& req, std::string* resp)> Handler;
- // 多进程版本的 Tcp 服务器
- class TcpProcessServer {
- public:
- TcpProcessServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port)
- {
- // 需要处理子进程
- signal(SIGCHLD, SIG_IGN);
- }
-
- void ProcessConnect(const TcpSocket& new_sock, const std::string& ip, uint16_t port,Handler handler)
- {
- int ret = fork();
- if (ret > 0)
- {
- // father
- // 父进程不需要做额外的操作, 直接返回即可.
- // 思考, 这里能否使用 wait 进行进程等待?
- // 如果使用 wait , 会导致父进程不能快速再次调用到 accept, 仍然没法处理多个请求
- // [注意!!] 父进程需要关闭 new_sock
- new_sock.Close();
- return;
- }
- else if (ret == 0)
- {
- // child
- // 处理具体的连接过程. 每个连接一个子进程
- for (;;)
- {
- std::string req;
- bool ret = new_sock.Recv(&req);
- if (!ret)
- {
- // 当前的请求处理完了, 可以退出子进程了. 注意, socket 的关闭在析构函数中就完
- // 成了
- printf("[client %s:%d] disconnected!\n", ip.c_str(), port);
- exit(0);
- }
- std::string resp;
- handler(req, &resp);
- new_sock.Send(resp);
- printf("[client %s:%d] req: %s, resp: %s\n", ip.c_str(), port,
- req.c_str(), resp.c_str());
- }
- }
- else
- {
- perror("fork");
- }
- }
-
- bool Start(Handler handler)
- {
- // 1. 创建 socket;
- CHECK_RET(listen_sock_.Socket());
- // 2. 绑定端口号
- CHECK_RET(listen_sock_.Bind(ip_, port_));
- // 3. 进行监听
- CHECK_RET(listen_sock_.Listen(5));
- // 4. 进入事件循环
- for (;;)
- {
- // 5. 进行 accept
- TcpSocket new_sock;
- std::string ip;
- uint16_t port = 0;
- if (!listen_sock_.Accept(&new_sock, &ip, &port))
- {
- continue;
- }
- printf("[client %s:%d] connect!\n", ip.c_str(), port);
- ProcessConnect(new_sock, ip, port, handler);
- }
- return true;
- }
-
- private:
- TcpSocket listen_sock_;
- std::string ip_;
- uint64_t port_;
- };
dict_server.cc 稍加修改
将 TcpServer 类改成 TcpProcessServer 类即可;
通过每个请求, 创建一个线程的方式来支持多连接;
tcp_thread_server.hpp
- #pragma once
- #include <functional>
- #include <pthread.h>
- #include "tcp_socket.hpp"
- typedef std::function<void (const std::string&, std::string*)> Handler;
- struct ThreadArg
- {
- TcpSocket new_sock;
- std::string ip;
- uint16_t port;
- Handler handler;
- };
-
- class TcpThreadServer
- {
- public:
- TcpThreadServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port)
- {}
-
- bool Start(Handler handler)
- {
- // 1. 创建 socket;
- CHECK_RET(listen_sock_.Socket());
- // 2. 绑定端口号
- CHECK_RET(listen_sock_.Bind(ip_, port_));
- // 3. 进行监听
- CHECK_RET(listen_sock_.Listen(5));
- // 4. 进入循环
- for (;;)
- {
- // 5. 进行 accept
- ThreadArg* arg = new ThreadArg();
- arg->handler = handler;
- bool ret = listen_sock_.Accept(&arg->new_sock, &arg->ip, &arg->port);
- if (!ret)
- {
- continue;
- }
- printf("[client %s:%d] connect\n", arg->ip.c_str(), arg->port);
- // 6. 创建新的线程完成具体操作
- pthread_t tid;
- pthread_create(&tid, NULL, ThreadEntry, arg);
- pthread_detach(tid);
- }
- return true;
- }
-
- // 这里的成员函数为啥非得是 static?
- static void* ThreadEntry(void* arg)
- {
- // C++ 的四种类型转换都是什么?
- ThreadArg* p = reinterpret_cast<ThreadArg*>(arg);
- ProcessConnect(p);
- // 一定要记得释放内存!!! 也要记得关闭文件描述符
- p->new_sock.Close();
- delete p;
- return NULL;
- }
-
- // 处理单次连接. 这个函数也得是 static
- static void ProcessConnect(ThreadArg* arg)
- {
- // 1. 循环进行读写
- for (;;)
- {
- std::string req;
- // 2. 读取请求
- bool ret = arg->new_sock.Recv(&req);
- if (!ret)
- {
- printf("[client %s:%d] disconnected!\n", arg->ip.c_str(), arg->port);
- break;
- }
- std::string resp;
- // 3. 根据请求计算响应
- arg->handler(req, &resp);
- // 4. 发送响应
- arg->new_sock.Send(resp);
- printf("[client %s:%d] req: %s, resp: %s\n", arg->ip.c_str(),
- arg->port, req.c_str(), resp.c_str());
- }
- }
- private:
- TcpSocket listen_sock_;
- std::string ip_;
- uint16_t port_;
- };
修改服务器为线程池版本即可;
下图是基于TCP协议的客户端/服务器程序的一般流程:
服务器初始化:
建立连接的过程:
这个建立连接的过程, 通常称为三次握手;
数据传输的过程:
断开连接的过程:
这个断开连接的过程, 通常称为 四次挥手
在学习socket API时要注意应用程序和TCP协议层是如何交互的:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。