赞
踩
目录
首先我们需要对服务端进行封装。我们需要的成员变量有IP地址,端口号port,以及监听套接字listensock。Tcp服务器实现的一般步骤就是:创建套接字,进行绑定,监听等待连接,获取连接,开始通信服务。下面我们一一讲解。
第一步,创建套接字使用的函数和UDP完全一样,只不过我们需要把socket的第二个参数由基于数据报的SOCK_DGRAM 更改为基于字节流式的SOCK_STREAM,SOCK_STREAM提供的就是一个有序的、可靠的、全双工的、基于连接的流式服务。其他参数和后续步骤完全一样。
第二步,进行绑定时使用的bind函数及其参数设置与UDP完全一样。
第三步,TCP和UDP不一样的地方就是这里了。前面我们知道UDP不是面向连接的,而TCP是面向连接的,所以服务器和客户端之间需要建立连接。而服务器首先需要做的就是进行监听,等待客户端的连接。需要使用listen函数。
- NAME
- listen - listen for connections on a socket
-
- SYNOPSIS
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
-
- int listen(int sockfd, int backlog);
参数说明:
sockfd:需要设置为监听状态的套接字对应的文件描述符。
backlog:全连接队列的最大长度。如果有多个客户端同时发来连接请求,此时未被服务器处理的连接就会放入连接队列,该参数代表的就是这个全连接队列的最大长度,一般不要设置太大,设置为5或10即可。
返回值:监听成功返回0,监听失败返回-1,同时错误码会被设置。
以上三步就是服务器的初始化。
第四步,TCP服务器初始化后就可以开始运行了,但TCP服务器在与客户端进行网络通信之前,服务器需要先获取到客户端的连接请求。我们需要使用accept函数。
- NAME
- accept, accept4 - accept a connection on a socket
-
- SYNOPSIS
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
-
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
sockfd:特定的监听套接字,表示从该监听套接字中获取连接。
addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
addrlen:调用时传入期望读取的addr结构体的长度,返回时代表实际读取到的addr结构体的长度,这是一个输入输出型参数。
返回值:获取连接成功返回接收到的套接字的文件描述符,获取连接失败返回-1,同时错误码会被设置。
注意:为什么获取连接成功了还要返回一个新的套接字呢?
类里面的监听套接字:用于获取客户端发来的连接请求。accept函数会不断从监听套接字当中获取新连接。
accept函数返回的套接字:用于为本次accept获取到的连接提供服务。
所以说,监听套接字的任务只是不断获取新连接,而真正为这些连接提供服务的套接字是accept函数返回的套接字,而不是监听套接字。
第五步,我们只要获取到客户端的连接,就可以对客户端进行服务了。
与UDP相同,我们需要创建TCP服务器对象,然后对其进行初始化,然后运行。我们也是只需要绑定端口号即可。
作为TCP客户端,首先也是需要创建套接字。然后向服务器发起连接,最后就可以开始通信了。和UDP一样,客户端不需要绑定端口号。
客户端向服务器发起连接需要使用connect函数。
- NAME
- connect - initiate a connection on a socket
-
- SYNOPSIS
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
-
- int connect(int sockfd, const struct sockaddr *addr,
- socklen_t addrlen);
参数说明:
sockfd:特定的套接字,表示通过该套接字发起连接请求。
addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
addrlen:传入的addr结构体的长度。
返回值:连接或绑定成功返回0,连接失败返回-1,同时错误码会被设置。
首先,我们要知道:对于TCP服务器和客户端,我们也可以使用write函数和read函数进行消息的读取和发送。
我们先写一个服务,实现客户端给服务器发送什么消息,服务器就返回什么消息。
我们先写一个单进程版本的服务。tcp_server.hpp:
- #include <iostream>
- #include <string>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <cassert>
- #include <signal.h>
-
- static int gmv = 20;
-
- static void service(int sock, const std::string &clientip, const uint16_t &clientport)
- {
- char buffer[1024];
- while (true)
- {
- ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
- if (s < 0)
- {
- std::cout << "接收信息失败!" << std::endl;
- break;
- }
- else if (s > 0)
- {
- buffer[s] = 0;
- std::cout << clientip << ":" << clientport << std::endl;
- }
- else
- {
- std::cout << "客户端关闭!" << std::endl;
- break;
- }
- write(sock, buffer, strlen(buffer));
- }
- }
-
- class TcpServer
- {
- public:
- TcpServer(const uint16_t port, const std::string &ip = "") : listensock_(-1), ip_(ip), port_(port)
- {
- }
-
- void initTcpServer()
- {
- // 1.创建套接字
- listensock_ = socket(AF_INET, SOCK_STREAM, 0);
- if (listensock_ < 0)
- {
- std::cout << "创建套接字失败!" << std::endl;
- exit(0);
- }
-
- // 2.进行绑定
- struct sockaddr_in src_server;
- bzero(&src_server, sizeof(src_server));
- src_server.sin_family = AF_INET;
- src_server.sin_port = htons(port_);
- inet_pton(AF_INET, ip_.c_str(), &src_server);
- socklen_t len = sizeof(src_server);
- if (bind(listensock_, (struct sockaddr *)&src_server, len) < 0)
- {
- std::cout << "绑定失败!" << std::endl;
- exit(1);
- }
-
- // 3.开始监听,等待连接
- if (listen(listensock_, gmv) < 0)
- {
- std::cout << "监听失败!" << std::endl;
- exit(2);
- }
-
- std::cout << "服务器初始化成功!" << std::endl;
- }
-
- void start()
- {
- //signal(SIGCHLD, SIG_IGN);
- while (true)
- {
- // 4.获取链接
- struct sockaddr_in client_sock;
- socklen_t len = sizeof(client_sock);
- int serversock = accept(listensock_, (struct sockaddr *)&client_sock, &len);
- if (serversock < 0)
- {
- std::cout << "获取链接失败!" << std::endl;
- exit(3);
- }
- // 5.开始通信服务
- uint16_t client_port = ntohs(client_sock.sin_port);
- std::string client_ip = inet_ntoa(client_sock.sin_addr);
- std::cout << "[" << client_port << "-" << client_ip << "]" << std::endl;
-
- //version 1:单进程版服务————服务器一次只能处理一个客户端,只有处理完一个,才能处理下一个
- service(serversock, client_ip, client_port);
- }
- }
-
- ~TcpServer()
- {
- close(listensock_);
- }
-
- private:
- int listensock_;
- std::string ip_;
- uint16_t port_;
- };
结果:
上面的结果没有什么问题,那么,如果我们同时有两个客户端访问服务器,会发生什么呢?
因为我们目前所写的是一个单执行流版的服务器,这个服务器一次只能为一个客户端提供服务。所以我们发现,当其中一个客户端在和服务器进行通信的时候,另一个客户端并不能与服务器通信,也就是说服务器在某一时刻只能向一个客户端提供服务,只有对一个客户端提供服务完成后,才能对下一个服务器提供服务。
很显然,这种提供服务的方式是不符合实际的,一个服务器应该要能够同时给多个客户端提供服务。所以我们需要改进代码。
多进程服务版本:既然单进程不符合实际,那么我们最先想到的就是使用多进程——创建子进程给新的连接提供服务,父进程去继续获取新的连接。
- #include <iostream>
- #include <string>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <cassert>
- #include <signal.h>
-
- static int gmv = 20;
-
- static void service(int sock, const std::string &clientip, const uint16_t &clientport)
- {
- char buffer[1024];
- while (true)
- {
- ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
- if (s < 0)
- {
- std::cout << "接收信息失败!" << std::endl;
- break;
- }
- else if (s > 0)
- {
- buffer[s] = 0;
- std::cout << clientip << ":" << clientport << std::endl;
- }
- else
- {
- std::cout << "客户端关闭!" << std::endl;
- break;
- }
- write(sock, buffer, strlen(buffer));
- }
- }
-
- class TcpServer
- {
- public:
- TcpServer(const uint16_t port, const std::string &ip = "") : listensock_(-1), ip_(ip), port_(port)
- {
- }
-
- void initTcpServer()
- {
- // 1.创建套接字
- listensock_ = socket(AF_INET, SOCK_STREAM, 0);
- if (listensock_ < 0)
- {
- std::cout << "创建套接字失败!" << std::endl;
- exit(0);
- }
-
- // 2.进行绑定
- struct sockaddr_in src_server;
- bzero(&src_server, sizeof(src_server));
- src_server.sin_family = AF_INET;
- src_server.sin_port = htons(port_);
- inet_pton(AF_INET, ip_.c_str(), &src_server);
- socklen_t len = sizeof(src_server);
- if (bind(listensock_, (struct sockaddr *)&src_server, len) < 0)
- {
- std::cout << "绑定失败!" << std::endl;
- exit(1);
- }
-
- // 3.开始监听,等待连接
- if (listen(listensock_, gmv) < 0)
- {
- std::cout << "监听失败!" << std::endl;
- exit(2);
- }
-
- std::cout << "服务器初始化成功!" << std::endl;
- }
-
- void start()
- {
- signal(SIGCHLD, SIG_IGN);
- while (true)
- {
- // 4.获取链接
- struct sockaddr_in client_sock;
- socklen_t len = sizeof(client_sock);
- int serversock = accept(listensock_, (struct sockaddr *)&client_sock, &len);
- if (serversock < 0)
- {
- std::cout << "获取链接失败!" << std::endl;
- exit(3);
- }
- // 5.开始通信服务
- uint16_t client_port = ntohs(client_sock.sin_port);
- std::string client_ip = inet_ntoa(client_sock.sin_addr);
- std::cout << "[" << client_port << "-" << client_ip << "]" << std::endl;
- // version 2.0:多进程版本——创建子进程给新的连接提供服务,父进程去继续获取新的连接
- pid_t id = fork();
- assert(id != -1);
- if (id == 0)
- {
- // child 并且注意僵尸状态
- // child会继承父进程打开的文件及其fd,子进程不需要进行监听的套接字,关闭
- close(listensock_);
- //子进程只需要给连接客户端提供服务的套接字
- service(serversock, client_ip, client_port);
- exit(0);
- }
- close(serversock);
- }
- }
-
- ~TcpServer()
- {
- close(listensock_);
- }
-
- private:
- int listensock_;
- std::string ip_;
- uint16_t port_;
- };
结果:
这种方法可以解决问题。
当然,我们还有如下的多进程版本:
-
- #include <iostream>
- #include <string>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <cassert>
- #include <signal.h>
-
- static int gmv = 20;
-
- static void service(int sock, const std::string &clientip, const uint16_t &clientport)
- {
- char buffer[1024];
- while (true)
- {
- ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
- if (s < 0)
- {
- std::cout << "接收信息失败!" << std::endl;
- break;
- }
- else if (s > 0)
- {
- buffer[s] = 0;
- std::cout << clientip << ":" << clientport << std::endl;
- }
- else
- {
- std::cout << "客户端关闭!" << std::endl;
- break;
- }
- write(sock, buffer, strlen(buffer));
- }
- }
-
- class TcpServer
- {
- public:
- TcpServer(const uint16_t port, const std::string &ip = "") : listensock_(-1), ip_(ip), port_(port)
- {
- }
-
- void initTcpServer()
- {
- // 1.创建套接字
- listensock_ = socket(AF_INET, SOCK_STREAM, 0);
- if (listensock_ < 0)
- {
- std::cout << "创建套接字失败!" << std::endl;
- exit(0);
- }
-
- // 2.进行绑定
- struct sockaddr_in src_server;
- bzero(&src_server, sizeof(src_server));
- src_server.sin_family = AF_INET;
- src_server.sin_port = htons(port_);
- inet_pton(AF_INET, ip_.c_str(), &src_server);
- socklen_t len = sizeof(src_server);
- if (bind(listensock_, (struct sockaddr *)&src_server, len) < 0)
- {
- std::cout << "绑定失败!" << std::endl;
- exit(1);
- }
-
- // 3.开始监听,等待连接
- if (listen(listensock_, gmv) < 0)
- {
- std::cout << "监听失败!" << std::endl;
- exit(2);
- }
-
- std::cout << "服务器初始化成功!" << std::endl;
- }
-
- void start()
- {
- while (true)
- {
- // 4.获取链接
- struct sockaddr_in client_sock;
- socklen_t len = sizeof(client_sock);
- int serversock = accept(listensock_, (struct sockaddr *)&client_sock, &len);
- if (serversock < 0)
- {
- std::cout << "获取链接失败!" << std::endl;
- exit(3);
- }
- // 5.开始通信服务
- uint16_t client_port = ntohs(client_sock.sin_port);
- std::string client_ip = inet_ntoa(client_sock.sin_addr);
- std::cout << "[" << client_port << "-" << client_ip << "]" << std::endl;
-
- // version 2.1:多进程版
- pid_t pid = fork();
- if (pid == 0)
- {
- // 子进程
- if (fork() > 0)
- exit(0);
- // 孙子进程
- close(listensock_);
- service(serversock, client_ip, client_port);
- }
- // 父进程
- waitpid(pid, nullptr, 0);
- close(serversock);
- }
- }
-
- ~TcpServer()
- {
- close(listensock_);
- }
-
- private:
- int listensock_;
- std::string ip_;
- uint16_t port_;
- };
我们让子进程创建完孙子进程后立刻退出,此时服务进程(父进程)调用wait/waitpid函数等待子进程就能立刻等待成功,此后服务进程就能继续调用accept函数获取其他客户端的连接请求。
而由于子进程退出,孙子进程就变成孤儿进程,被os领养,最后由os自动回收。
结果:
多线程版本:我们还可以创建新的线程去对客户端进行服务,让主线程继续获取连接:
- #include <iostream>
- #include <string>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <cassert>
- #include <signal.h>
-
- static int gmv = 20;
-
- static void service(int sock, const std::string &clientip, const uint16_t &clientport)
- {
- char buffer[1024];
- while (true)
- {
- ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
- if (s < 0)
- {
- std::cout << "接收信息失败!" << std::endl;
- break;
- }
- else if (s > 0)
- {
- buffer[s] = 0;
- std::cout << clientip << ":" << clientport << std::endl;
- }
- else
- {
- std::cout << "客户端关闭!" << std::endl;
- break;
- }
- write(sock, buffer, strlen(buffer));
- }
- }
-
- class threaddata
- {
- public:
- int sock_;
- std::string ip_;
- uint16_t port_;
- };
-
- class TcpServer
- {
- public:
- static void *threadRountine(void *arg)
- {
- pthread_detach(pthread_self());
- threaddata *td = static_cast<threaddata *>(arg);
- service(td->sock_, td->ip_, td->port_);
- delete td;
- }
-
- TcpServer(const uint16_t port, const std::string &ip = "") : listensock_(-1), ip_(ip), port_(port)
- {
- }
-
- void initTcpServer()
- {
- // 1.创建套接字
- listensock_ = socket(AF_INET, SOCK_STREAM, 0);
- if (listensock_ < 0)
- {
- std::cout << "创建套接字失败!" << std::endl;
- exit(0);
- }
-
- // 2.进行绑定
- struct sockaddr_in src_server;
- bzero(&src_server, sizeof(src_server));
- src_server.sin_family = AF_INET;
- src_server.sin_port = htons(port_);
- inet_pton(AF_INET, ip_.c_str(), &src_server);
- socklen_t len = sizeof(src_server);
- if (bind(listensock_, (struct sockaddr *)&src_server, len) < 0)
- {
- std::cout << "绑定失败!" << std::endl;
- exit(1);
- }
-
- // 3.开始监听,等待连接
- if (listen(listensock_, gmv) < 0)
- {
- std::cout << "监听失败!" << std::endl;
- exit(2);
- }
-
- std::cout << "服务器初始化成功!" << std::endl;
- }
-
- void start()
- {
- while (true)
- {
- // 4.获取链接
- struct sockaddr_in client_sock;
- socklen_t len = sizeof(client_sock);
- int serversock = accept(listensock_, (struct sockaddr *)&client_sock, &len);
- if (serversock < 0)
- {
- std::cout << "获取链接失败!" << std::endl;
- exit(3);
- }
- // 5.开始通信服务
- uint16_t client_port = ntohs(client_sock.sin_port);
- std::string client_ip = inet_ntoa(client_sock.sin_addr);
- std::cout << "[" << client_port << "-" << client_ip << "]" << std::endl;
-
- // version 3——多线程版本
- pthread_t tid;
- threaddata *td = new threaddata();
- td->sock_ = serversock;
- td->ip_ = client_ip;
- td->port_ = client_port;
- pthread_create(&tid, nullptr, threadRountine, td);
- }
- }
-
- ~TcpServer()
- {
- close(listensock_);
- }
-
- private:
- int listensock_;
- std::string ip_;
- uint16_t port_;
- };
tcp_server.hpp
- #include <iostream>
- #include <string>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <cassert>
- #include <signal.h>
-
- static int gmv = 20;
-
- static void service(int sock, const std::string &clientip, const uint16_t &clientport)
- {
- char buffer[1024];
- while (true)
- {
- ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
- if (s < 0)
- {
- std::cout << "接收信息失败!" << std::endl;
- break;
- }
- else if (s > 0)
- {
- buffer[s] = 0;
- std::cout << clientip << ":" << clientport << std::endl;
- }
- else
- {
- std::cout << "客户端关闭!" << std::endl;
- }
- write(sock, buffer, strlen(buffer));
- }
- }
-
- class threaddata
- {
- public:
- int sock_;
- std::string ip_;
- uint16_t port_;
- };
-
- class TcpServer
- {
- public:
- static void *threadRountine(void *arg)
- {
- pthread_detach(pthread_self());
- threaddata *td = static_cast<threaddata *>(arg);
- service(td->sock_, td->ip_, td->port_);
- delete td;
- }
-
- TcpServer(const uint16_t port, const std::string &ip = "") : listensock_(-1), ip_(ip), port_(port)
- {
- }
-
- void initTcpServer()
- {
- // 1.创建套接字
- listensock_ = socket(AF_INET, SOCK_STREAM, 0);
- if (listensock_ < 0)
- {
- std::cout << "创建套接字失败!" << std::endl;
- exit(0);
- }
-
- // 2.进行绑定
- struct sockaddr_in src_server;
- bzero(&src_server, sizeof(src_server));
- src_server.sin_family = AF_INET;
- src_server.sin_port = htons(port_);
- inet_pton(AF_INET, ip_.c_str(), &src_server);
- socklen_t len = sizeof(src_server);
- if (bind(listensock_, (struct sockaddr *)&src_server, len) < 0)
- {
- std::cout << "绑定失败!" << std::endl;
- exit(1);
- }
-
- // 3.开始监听,等待连接
- if (listen(listensock_, gmv) < 0)
- {
- std::cout << "监听失败!" << std::endl;
- exit(2);
- }
-
- std::cout << "服务器初始化成功!" << std::endl;
- }
-
- void start()
- {
- signal(SIGCHLD, SIG_IGN);
- while (true)
- {
- // 4.获取链接
- struct sockaddr_in client_sock;
- socklen_t len = sizeof(client_sock);
- int serversock = accept(listensock_, (struct sockaddr *)&client_sock, &len);
- if (serversock < 0)
- {
- std::cout << "获取链接失败!" << std::endl;
- exit(3);
- }
- // 5.开始通信服务
- uint16_t client_port = ntohs(client_sock.sin_port);
- std::string client_ip = inet_ntoa(client_sock.sin_addr);
- std::cout << "[" << client_port << "-" << client_ip << "]" << std::endl;
-
- // version 1:单进程版服务————服务器一次只能处理一个客户端,只有处理完一个,才能处理下一个
- // service(serversock, client_ip, client_port);
-
- // version 2.0:多进程版本——创建子进程给新的连接提供服务,父进程去继续获取新的连接
- /* pid_t id = fork();
- assert(id != -1);
- if (id == 0)
- {
- // child 并且注意僵尸状态
- // child会继承父进程打开的文件及其fd,子进程不需要进行监听的套接字,关闭
- close(listensock_);
- //子进程只需要给连接客户端提供服务的套接字
- service(serversock, client_ip, client_port);
- exit(0);
- } */
-
- /* // version 2.1:多进程版
- pid_t pid = fork();
- if (pid == 0)
- {
- // 子进程
- if (fork() > 0)
- exit(0);
- // 孙子进程
- close(listensock_);
- service(serversock, client_ip, client_port);
- }
- // 父进程
- waitpid(pid, nullptr, 0); */
-
- // version 3——多线程版本
- pthread_t tid;
- threaddata *td = new threaddata();
- td->sock_ = serversock;
- td->ip_ = client_ip;
- td->port_ = client_port;
- pthread_create(&tid, nullptr, threadRountine, td);
-
- close(serversock);
- }
- }
-
- ~TcpServer()
- {
- close(listensock_);
- }
-
- private:
- int listensock_;
- std::string ip_;
- uint16_t port_;
- };
tcp_server.cc
- #include "tcp_server.hpp"
- #include <memory>
-
- static void usage(std::string proc)
- {
- std::cout << "\n"
- << proc << " port"
- << std::endl;
- }
-
- // ./tcpserver 8080
- int main(int argc, char *argv[])
- {
- if (argc != 2)
- {
- usage(argv[0]);
- exit(0);
- }
- uint16_t server_port=atoi(argv[1]);
- std::unique_ptr<TcpServer> sev(new TcpServer(server_port));
- sev->initTcpServer();
- sev->start();
-
- return 0;
- }
tcp_client.cc
- #include <iostream>
- #include <sys/types.h>
- #include <unistd.h>
- #include <string.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
-
- static void usage(std::string proc)
- {
- std::cout << "\n"
- << proc << " port"
- << std::endl;
- }
-
- // ./tcpclient IP port
- int main(int argc, char *argv[])
- {
- if (argc != 3)
- {
- usage(argv[0]);
- exit(1);
- }
- std::string server_ip = argv[1];
- uint16_t server_port = atoi(argv[2]);
-
- int sock = socket(AF_INET, SOCK_STREAM, 0);
- if (sock < 0)
- {
- std::cout << "创建套接字失败!" << std::endl;
- exit(0);
- }
-
- struct sockaddr_in server;
- memset(&server, 0, sizeof server);
- server.sin_family = AF_INET;
- server.sin_port = htons(server_port);
- server.sin_addr.s_addr = inet_addr(server_ip.c_str());
- if (connect(sock, (struct sockaddr *)&server, sizeof server) < 0)
- {
- std::cout << "连接失败!" << std::endl;
- exit(0);
- }
-
- // 开始通信
- while (true)
- {
- std::string line;
- std::cout << "请输入# ";
- std::getline(std::cin, line);
- ssize_t s = write(sock, line.c_str(), line.size());
-
- char buffer[1024];
- ssize_t m = read(sock, buffer, sizeof buffer - 1);
- buffer[m] = 0;
- std::cout << "服务器:" << buffer << std::endl;
- }
-
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。