网站建设与管理规定,怎么做网站流量赚钱吗,代运营公司哪家好,陈铭生简介#x1f941;作者#xff1a; 华丞臧. #x1f4d5;专栏#xff1a;【网络】 各位读者老爷如果觉得博主写的不错#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方#xff0c;欢迎在评论区指出。 推荐一款刷题网站 #x1f449; LeetCode刷题网站 文章… 作者 华丞臧. 专栏【网络】 各位读者老爷如果觉得博主写的不错请诸位多多支持(点赞收藏关注)。如果有错误的地方欢迎在评论区指出。 推荐一款刷题网站 LeetCode刷题网站 文章目录一、TCP协议1.1 socket 常见APIlistenacceptconnect1.2 TCP服务器init()start()1.3 TCP客户端1.4 测试一、TCP协议
对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识
传输层协议有连接可靠传输面向字节流
1.1 socket 常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)
int socket(int domain, int type, int protocol);socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;应用程序可以像读写文件一样用read/write在网络上收发数据;如果socket()调用出错则返回-1;对于IPv4, family参数指定为AF_INET;对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议protocol参数的介绍从略,指定为0即可。
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;bind()成功返回0,失败返回-1。bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号;前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;
程序中对 sockaddr 参数是这样初始化的
#include netinet/in.h
#include arpa/inet.h// 2.填充域
struct sockaddr_in local;
bzero(local, sizeof(local));
local.sin_family AF_INET;
local.sin_port htons(port_);将整个结构体清零设置地址类型为AF_INET网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址端口号为SERV_PORT, 我们定义为9999。
listen
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5)listen()成功返回0,失败返回-1。
accept
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);三次握手完成后, 服务器调用accept()接受连接;如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;addr是一个传出参数,accept()返回时传出客户端的地址和端口号;如果给addr 参数传NULL,表示不关心客户端的地址;addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
connect
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);客户端需要调用connect()连接服务器;connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;connect()成功返回0,出错返回-1;
1.2 TCP服务器 #include iostream
#include stdio.h
#include strings.h
#include unistd.h
#include string.h
#include errno.h
#include pthread.h
#include netinet/in.h
#include arpa/inet.h
#include sys/types.h
#include sys/socket.h#include Log.hppvolatile bool quitSer false;void Usage(void *vgs)
{std::cout Usage:./tcpserver port ip std::endl;
}
class server
{
public:server(int port, std::string ip ): sockfd_(-1), ip_(ip), port_(port){}~server(){}public:void init(){}void start(){}private:int sockfd_;uint16_t port_;std::string ip_;
};// ./tcpserver port ip
int main(int argc, char *argv[])
{if (argc 2 || argc 3){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);std::string ip;if (argc 3)ip argv[2];server tcpSer(port, ip);tcpSer.init();tcpSer.start();return 0;
}init()
TCP套接字服务器端的 init() 与UDP类似只不过多了最后一步监听套接字TCP套接字设置步骤如下 void init()
{// 1. 创建套接字sockfd_ socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ 0){logMessage(FATAL, socket:%s[%d], strerror(errno), sockfd_);exit(SOCK_ERR);}logMessage(DEBUG, socket success..);// 2.填充域struct sockaddr_in local;bzero(local, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port_);ip_.empty() ? (local.sin_addr.s_addr INADDR_ANY) : (inet_aton(ip_.c_str(), local.sin_addr));// 3. 绑定网络信息if (bind(sockfd_, (const struct sockaddr *)local, sizeof(local)) 0){logMessage(FATAL, bind:%s[%d], strerror(errno), sockfd_);exit(BIND_ERR);}logMessage(DEBUG, bind success...);// 4. 监听套接字// 为什么要监听套接字 因为TCP是面向连接的在任何时候都可能请求连接 if (listen(sockfd_, 5) 0){logMessage(FATAL, listen:%s[%d], strerror(errno), sockfd_);exit(LISTEN_ERR);}logMessage(DEBUG, listen success...);// 完成
}start()
在TCP套接字提供服务之前需要使用accept获取连接accept返回值是一个新的套接字文件描述符使用该套接字文件描述符来进行网络通信。start()是server提供服务的接口因此该函数必须是一个死循环服务器都是在一个死循环当中以给用户提供持续的服务在start函数中主要完成接收用户发送的消息并且将消息提取出来其主要步骤如下图
void start()
{char inbuffer_[1024]; // 用来接收客户端发来的消息// 提供服务while (true){quitSer false;struct sockaddr_in peer;socklen_t len sizeof(peer);// 5. 获取连接, accept 的返回值是一个新的 socketfdint serviceSock accept(sockfd_, (struct sockaddr *)peer, len);if (serviceSock 0){// 获取链接失败logMessage(WARINING, accept: %s[%d], strerror(errno), serviceSock);continue;}while (!quitSer){memset(inbuffer_, 0, sizeof(inbuffer_));ssize_t s recvfrom(serviceSock, inbuffer_, sizeof(inbuffer_) - 1, 0,(struct sockaddr *)peer, len);if (s 0){std::cout s std::endl;// 接收成功inbuffer_[s] \0;}else if (s -1){//logMessage(WARINING, recvfrom fialed:%s[%d], strerror(errno), sockfd_);continue;}uint16_t peerPort ntohs(peer.sin_port);std::string peerIp inet_ntoa(peer.sin_addr);if(s 0)logMessage(NOTICE, [%s:%d]# %s, peerIp.c_str(), peerPort, inbuffer_);if (strcmp(inbuffer_, quit) 0){quitSer true;}else{sendto(serviceSock, inbuffer_, strlen(inbuffer_), 0,\(const struct sockaddr *)peer, sizeof(peer)); }}logMessage(DEBUG, quit server...);close(serviceSock);}
}1.3 TCP客户端
TCP客户端与UDP不同的是在进行网络通信之前需要对服务器发起连接请求连接成功后才能进行网络通信。
#include iostream
#include string
#include cstring
#include cstdlib
#include cassert
#include ctype.h
#include unistd.h
#include strings.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include Log.hppvolatile bool quit false;static void Usage(std::string proc)
{std::cerr Usage:\n\t proc serverIp serverPort std::endl;std::cerr Example:\n\t proc 127.0.0.1 8081\n std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{if (argc ! 3){Usage(argv[0]);exit(USAGE_ERR);}std::string serverIp argv[1];uint16_t serverPort atoi(argv[2]);// 1. 创建socket SOCK_STREAMint sock socket(AF_INET, SOCK_STREAM, 0);if (sock 0){std::cerr socket: strerror(errno) std::endl;exit(SOCK_ERR);}// 2. connect发起连接请求你想谁发起请求呢当然是向服务器发起请求喽// 2.1 先填充需要连接的远端主机的基本信息struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverPort);inet_aton(serverIp.c_str(), server.sin_addr);// 2.2 发起请求connect 会自动帮我们进行bindif (connect(sock, (const struct sockaddr *)server, sizeof(server)) ! 0){std::cerr connect: strerror(errno) std::endl;exit(CONN_ERR);}std::cout info : connect success: sock std::endl;std::string message;while (!quit){message.clear();std::cout 请输入你的消息 ;std::getline(std::cin, message);if (strcasecmp(message.c_str(), quit) 0)quit true;ssize_t s write(sock, message.c_str(), message.size());logMessage(DEBUG, write success...);if (s 0){message.resize(1024);ssize_t s read(sock, (char *)(message.c_str()), 1024);if (s 0)message[s] 0;std::cout Server Echo message std::endl;}else if (s 0){break;}}close(sock);return 0;
}由于客户端不需要固定的端口号因此不必调用bind(),客户端的端口号由内核自动分配。
注意
客户端不是不允许调用bind()只是没有必要调用bind()固定一个端口号。否则如果在同一台机器上启动多个客户端, 就会出现端口号被占用导致不能正确建立连接服务器也不是必须调用bind()但如果服务器不调用bind()内核会自动给服务器分配监听端口每次启动服务器时端口号都不一样客户端要连接服务器就会遇到麻烦。
1.4 测试