网站备案帐户有什么用,高品质网站建设公司,嘉兴网站开发公司电话,萧山做网站公司目录
1 - TCP socket API
2 - V1 -Echo Server
2.1 - 测试多个连接的情况 1 - TCP socket API
socket()#xff1a; socket()打开一个网络通讯端口#xff0c;如果成功的话#xff0c;就像open()一样返回一个文件描述符。应用程序可以像读写文件一样用r…目录
1 - TCP socket API
2 - V1 -Echo Server
2.1 - 测试多个连接的情况 1 - TCP socket API
socket() socket()打开一个网络通讯端口如果成功的话就像open()一样返回一个文件描述符。应用程序可以像读写文件一样用read/write在网络上收发数据。如果socket()调用出错则返回-1。对于IPv4family参数指定为AF_INET。对于TCP协议type参数指定为SOCK_STREAM表示面向流的传输协议。protocol参数的介绍从略指定为0即可。
bind() 服务器程序所监听的网络地址和端口号通常是固定不变的客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接服务器需要调用bind绑定一个固定的网络地址和端口号。bind()成功返回0失败返回-1。bind()的作用是将参数sockfd和myaddr绑定在一起使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。struct sockaddr *是一个通用指针类型myaddr参数实际上可以接受多种协议的sockaddr结构体而它们的长度各不相同所以需要第三个参数addrlen指定结构体的长度。
我们的程序中对myaddr参数是这样初始化的 将整个结构体清零。设置地址类型为AF_INET。网络地址为INADDR_ANY这个宏表示本地的任意IP地址因为服务器可能有多个网卡每个网卡也可能绑定多个IP地址这样设置可以在所有的IP地址上监听直到与某个客户端建立了连接时才确定下来到底用哪个IP地址。端口号为SERV_PORT定义为9999。
listen() listen()声明sockfd处于监听状态并且最多允许有backlog个客户端处于连接等待状态如果接收到更多的连接请求就忽略这里设置不会太大(一般是5)。listen()成功返回0失败返回-1。
accept() 三次握手完成后服务器调用accept()接受连接。如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。addr是一个传出参数accept()返回时传出客户端的地址和端口号。如果给addr参数传NULL表示不关心客户端的地址。addrlen参数是一个传入传出参数(value-result argument)传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。
我们的服务器程序结构是这样的 connect 客户端需要调用connect()连接服务器。connect和bind的参数形式一致区别在于bind的参数是自己的地址而connect的参数是对方的地址。connect()成功返回0出错返回-1。
2 - V1 -Echo Server
nocopy.hpp
#pragma once
#include iostreamclass nocopy
{
public:nocopy() {}nocopy(const nocopy) delete;const nocopy operator (const nocopy) delete;~nocopy() {}
};
TcpServer.hpp
#pragma once
#include iostream
#include string
#include cerrno
#include cstring
#include cstdlib
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include Log.hpp
#include nocopy.hpp
#include Comm.hppconst static int default_backlog 6; // TODO
class TcpServer : public nocopy
{
public:TcpServer(uint16_t port) : _port(port), _isrunning(false){}// 都是固定套路void Init(){// 1. 创建 socket, file fd, 本质是文件_listensock socket(AF_INET, SOCK_STREAM, 0);if (_listensock 0){lg.LogMessage(Fatal, create socket error, errnocode: % d, error string : % s\n, errno, strerror(errno));exit(Fatal);}int opt 1;setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR |SO_REUSEPORT, opt, sizeof(opt));lg.LogMessage(Debug, create socket success,sockfd: % d\n, _listensock);// 2. 填充本地网络信息并 bindstruct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(_port);local.sin_addr.s_addr htonl(INADDR_ANY);// 2.1 bindif (bind(_listensock, CONV(local), sizeof(local)) ! 0){lg.LogMessage(Fatal, bind socket error, errnocode: % d, error string : % s\n, errno, strerror(errno));exit(Bind_Err);}lg.LogMessage(Debug, bind socket success, sockfd: %d\n,_listensock);// 3. 设置 socket 为监听状态tcp 特有的if (listen(_listensock, default_backlog) ! 0){lg.LogMessage(Fatal, listen socket error, errnocode: % d, error string : % s\n, errno, strerror(errno));exit(Listen_Err);}lg.LogMessage(Debug, listen socket success,sockfd: % d\n, _listensock);}// Tcp 连接全双工通信的.void Service(int sockfd){char buffer[1024];// 一直进行 IOwhile (true){ssize_t n read(sockfd, buffer, sizeof(buffer) - 1);if (n 0){buffer[n] 0;std::cout client say# buffer std::endl;std::string echo_string server echo# ;echo_string buffer;write(sockfd, echo_string.c_str(),echo_string.size());}else if (n 0) // read 如果返回值是 0表示读到了文件结尾(对端关闭了连接){lg.LogMessage(Info, client quit...\n);break;}else{lg.LogMessage(Error, read socket error, errnocode: % d, error string : % s\n, errno, strerror(errno));break;}}}void Start(){_isrunning true;while (_isrunning){// 4. 获取连接struct sockaddr_in peer;socklen_t len sizeof(peer);int sockfd accept(_listensock, CONV(peer), len);if (sockfd 0){lg.LogMessage(Warning, accept socket error, errnocode: % d, error string : % s\n, errno, strerror(errno));continue;}lg.LogMessage(Debug, accept success, get n newsockfd: % d\n, sockfd);// 5. 提供服务, v1~v4// v1// Service(sockfd);close(sockfd);}}~TcpServer(){}
private:uint16_t _port;int _listensock; // TODObool _isrunning;
};
Comm.hpp
#pragma once
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.henum
{Usage_Err 1,Socket_Err,Bind_Err,Listen_Err
};#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)
TcpClient.hpp
#include iostream
#include string
#include cstring
#include cstdlib
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include Comm.hpp
using namespace std;void Usage(const std::string process)
{std::cout Usage: process server_ip server_port std::endl;
}// ./tcp_client serverip serverport
int main(int argc, char* argv[])
{if (argc ! 3){Usage(argv[0]);return 1;}std::string serverip argv[1];uint16_t serverport stoi(argv[2]);// 1. 创建 socketint sockfd socket(AF_INET, SOCK_STREAM, 0);if (sockfd 0){cerr socket error endl;return 1;}// 2. 要不要 bind必须要有 Ip 和 Port, 需要 bind但是不需要用户显示的 bindclient 系统随机端口// 发起连接的时候client 会被 OS 自动进行本地绑定// // 2. connectstruct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverport);// p:process(进程), n(网络) -- 不太准确但是好记忆inet_pton(AF_INET, serverip.c_str(), server.sin_addr); // 1. 字符串 ip-4 字节 IP 2. 网络序列int n connect(sockfd, CONV(server), sizeof(server)); // 自动进行 bind 哦if (n 0){cerr connect error endl;return 2;}// 并没有向 server 一样产生新的 sockfd.未来我们就用 connect 成功的sockfd 进行通信即可.while (true){string inbuffer;cout Please Enter# ;getline(cin, inbuffer);ssize_t n write(sockfd, inbuffer.c_str(),inbuffer.size());if (n 0){char buffer[1024];ssize_t m read(sockfd, buffer, sizeof(buffer) - 1);if (m 0){buffer[m] 0;cout get a echo messsge - buffer endl;}else if (m 0 || m 0){break;}}else{break;}}close(sockfd);return 0;
}
由于客户端不需要固定的端口号因此不必调用bind()客户端的端口号由内核自动分配。
注意
客户端不是不允许调用bind()只是没有必要显示的调用bind()固定一个端口号。否则如果在同一台机器上启动多个客户端就会出现端口号被占用导致不能正确建立连接。服务器也不是必须调用bind()但如果服务器不调用bind()内核会自动给服务器分配监听端口每次启动服务器时端口号都不一样客户端要连接服务器就会遇到麻烦。
2.1 - 测试多个连接的情况
再启动一个客户端尝试连接服务器发现第二个客户端不能正确的和服务器进行通信。
分析原因是因为我们accecpt了一个请求之后就在一直while循环尝试read没有继续调用到 accecpt导致不能接受新的请求。
我们当前的这个TCP只能处理一个连接这是不科学的。 感谢各位大佬支持
互三啦