计算机网络(tcp_socket )
计算机网络(tcp_socket )(一)
- 1 有关tcp socket API
- 1.1 创建套接字
- 1.2 bind
- 1.3 建立连接
- 1.4 获取新的连接(accept)
- 1.5 读、写信息(read,write)
- 1.6 客户端发消息
- 2 代码一(单进程客户端通信)
- 3 代码一(多进程客户端通信)
1 有关tcp socket API
1.1 创建套接字
(1)TCP通信和UDP通信一样我们首先需要创建套接字(socket)
参数一:网络通信(AF_INET)
参数二:套接字类型(SOCK_STREAM)流式套接
参数三:0(默认就为TCP通信)
1.2 bind
这里跟UDP是一样的
总的来说,初始化服务器端,基本与UDP一致,仅有流式和数据报式的差别
1.3 建立连接
(1)将socket设置为监听状态
参数一:文件描述符(socket)
参数二:后序介绍(这里该参数不能为0,最好设置为16/32)
// 3 TCP是面向连接的,所以TCP随时随地等待被连接n = ::listen(_listensockfd, BACKLOG); // 需要将socket设置为监听状态if(n<0){LOG(LogLevel::FATAL) << "listen error";Die(LISTEN_ERR);}
1.4 获取新的连接(accept)
(1)accept:从指定文件描述符(只负责接收新的连接)中获取新的连接 / 客户端
参数一:监听文件描述符(这里的fd只负责接收新的连接)
参数二:相当于UDP中recvfrom的倒数第二个参数
参数三:相当于UDP中recvfrom的最后一个参数
返回值:
成功:非0(文件描述符,真正提供服务的),失败:-1,没有客户端连接默认阻塞
1.5 读、写信息(read,write)
(1)TCP是通过文件描述符来实现功能的,所以TCP比UDP更像文件,且接口也是read
(2)写文件也是直接通过文件描述符来写
1.6 客户端发消息
(1)我们之前说过客户端是不需要显示的进行bind的,因为端口号是OS自动帮我们分配的,之前UDP发消息有sendto(参数里包含IP和PORT),但是TCP用的是write和read,明显用这个接口IP和PORT传入不了
(2)connect
参数一、文件描述符
参数二、ip地址和端口号
参数三、sockaddr的长度
2 代码一(单进程客户端通信)
(1)makefile
(2)TcpServer.hpp
#pragma once
#include <iostream>
#include <memory>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"using namespace LogModule;
const static uint16_t gport = 8080;
#define BACKLOG 8class TcpServer
{
public:TcpServer(int port = gport) : _port(port), _isrunning(false){}void InitServer(){// 1 创建socket_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::FATAL) << "socket error!";Die(SOCK_ERR);}LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;// 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 = INADDR_ANY; // 云服务器建议绑任意IP地址int n = ::bind(_listensockfd, CONV(&local), sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error";Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";// 3 TCP是面向连接的,所以TCP随时随地等待被连接n = ::listen(_listensockfd, BACKLOG); // 需要将socket设置为监听状态if (n < 0){LOG(LogLevel::FATAL) << "listen error";Die(LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success";}void HandlerRequest(int sockfd){char inbuff[4096];while (1){ssize_t n = ::read(sockfd, inbuff, sizeof(inbuff) - 1);LOG(LogLevel::INFO) << inbuff;if (n > 0){inbuff[n] = 0;std::string echo_str = "server echo# ";echo_str += inbuff;::write(sockfd, echo_str.c_str(), echo_str.size());}else if (n == 0)std::cout << "client quite" << std::endl;elsebreak;}::close(sockfd); // 文件描述符是有限的,如果不关会出现fd泄漏}void Start(){_isrunning = true;while (_isrunning){// 1、不断获取新的连接struct sockaddr_in peer;socklen_t peerlen=sizeof(peer);// 我们可以通过peer来获取客户端的ip和端口号int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error";continue;}// 获取连接成功LOG(LogLevel::INFO) << "accept success, sockfd is: " << sockfd;InetAddr addr(peer);LOG(LogLevel::INFO) << "client info: " << addr.Addr();HandlerRequest(sockfd); // 处理请求}}void Stop(){_isrunning = false;}~TcpServer() {}private:int _listensockfd;uint16_t _port;bool _isrunning;
};
(3)TcpServerMain.cc
(4)TcpClientMain.cc
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// ./client server_ip server_port
int main(int argc, char *argv[])
{if (argc < 3){std::cout << "Usage error : ./client server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];int server_port = std::stoi(argv[2]);int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cout << "socket create error" << std::endl;return 2;}// client 客户端不需要显示的bindstruct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(server_port);server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());// connect 底层会自动进行bindint n = ::connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));if (n < 0){std::cout << "connect error" << std::endl;return 3;}// echo clientstd::string message;while (1){char inbuff[1024];std::cout << "Please Enter $ ";std::getline(std::cin, message);n = ::write(sockfd, message.c_str(), message.size());if (n > 0){int m = ::read(sockfd, inbuff, sizeof(inbuff));if (m > 0){inbuff[m] = 0;std::cout << inbuff << std::endl;}elsebreak;}elsebreak;}::close(sockfd);return 0;
}
(5)需要的文件目录
3 代码一(多进程客户端通信)
只需要更改
#pragma once
#include <iostream>
#include <memory>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include<signal.h>#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"using namespace LogModule;
const static uint16_t gport = 8080;
#define BACKLOG 8class TcpServer
{
public:TcpServer(int port = gport) : _port(port), _isrunning(false){}void InitServer(){// 1 创建socket_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::FATAL) << "socket error!";Die(SOCK_ERR);}LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;// 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 = INADDR_ANY; // 云服务器建议绑任意IP地址int n = ::bind(_listensockfd, CONV(&local), sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error";Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";// 3 TCP是面向连接的,所以TCP随时随地等待被连接n = ::listen(_listensockfd, BACKLOG); // 需要将socket设置为监听状态if (n < 0){LOG(LogLevel::FATAL) << "listen error";Die(LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success";}void HandlerRequest(int sockfd){char inbuff[4096];while (1){ssize_t n = ::read(sockfd, inbuff, sizeof(inbuff) - 1);LOG(LogLevel::INFO) << inbuff;if (n > 0){inbuff[n] = 0;std::string echo_str = "server echo# ";echo_str += inbuff;::write(sockfd, echo_str.c_str(), echo_str.size());}else if (n == 0){std::cout << "client quite" << std::endl;break;}elsebreak;}::close(sockfd); // 文件描述符是有限的,如果不关会出现fd泄漏::signal(SIGCHLD,SIG_IGN);//忽略子进程的退出信号//OS会自动回收资源,不用waitpid了}void Start(){_isrunning = true;while (_isrunning){// 1、不断获取新的连接struct sockaddr_in peer;socklen_t peerlen=sizeof(peer);// 我们可以通过peer来获取客户端的ip和端口号int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error";continue;}// 获取连接成功LOG(LogLevel::INFO) << "accept success, sockfd is: " << sockfd;InetAddr addr(peer);LOG(LogLevel::INFO) << "client info: " << addr.Addr();// HandlerRequest(sockfd); // 处理请求pid_t id = fork();if (id == 0){// child::close(_listensockfd); // 关闭子进程多余的文件描述符HandlerRequest(sockfd);exit(0);}::close(sockfd); // 关闭父进程多余的文件描述符}}void Stop(){_isrunning = false;}~TcpServer() {}private:int _listensockfd;uint16_t _port;bool _isrunning;
};