创建套接字:(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);
绑定端口号:(TCP/UDP,服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
监听套接字:(TCP,服务器)
int listen(int sockfd, int backlog);
接收请求:(TCP,服务器)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
建立连接:(TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
与学习UDP的接口相同,我们通过实现一个简单的TCP服务器与TCP客户端学习这些接口。首先我们编写服务器。
我们同样使用一个类来实现服务器,TCP下的网络通信同样需要创建套接字,使用TCP创建套接字的与UDP下大致相同
- //UDP
- int socket_fd=socket(AF_INET,SOCK_DGRAM,0);
-
- //TCP
- int socket_fd=socker(AF_TNET,SOCK_STREAM,0);
与UDP协议不同,TCP创建套接字的第二个参数应该选择 SOCK_STREAM。
UDP是无连接的,TCP是有连接的。所以UDP采用的是数据报传输方式,TCP采用的是字节流传输方式。
创建套接字
-
- class Server
- {
- public:
- Server(std::string ip,uint16_t port)
- :_sockfd(-1);
- :_ip(ip);
- :_port(port);
- {}
-
- bool init()
- {
- //创建套接字
- _sockfd = socket(AF_INET,SOCK_STREAM,0);
- if(_sockfd<0)
- {
- std::cerr << "socket flase" << std::endl;
- return false;
- }
-
- }
-
- ~Server()
- {
- if(_sockfd>=0)
- {
- close(_sockfd);
- }
- }
-
- private:
- int _sockfd;
- std::string _ip;
- uint16_t _port;
- };
绑定ip与端口
与我们编写的UDP服务端相同我们需要绑定IP与端口。流程与UDP协议相同。
- bool init()
- {
- //创建套接字
- _sockfd = socket(AF_INET,SOCK_STREAM,0);
- if(_sockfd<0)
- {
- std::cerr << "socket flase" << std::endl;
- return false;
- }
- //绑定端口
- struct sockaddr_in local;
- memset(&local,'\0',sizeof(local));
- //协议家族
- local.sin_family=AF_INET;
- //端口号
- local.sin_port=htons(_port);
- //ip
- local.sin_addr.s_addr = inet_addr(_ip.c_str());
- //绑定
- if(bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr_in)) < 0 )
- {
- //绑定失败
- std::cerr << "bind error" << std::endl;
- return false;
- }
- }
设置为监听
TCP是面向连接的,客户端与服务端需要建立连接后才能通信。所以TCP服务器需要时刻注意有无客户端的连接请求。我们需要将套接字设置为监听状态。
监听套接字:(TCP,服务器)
int listen(int sockfd, int backlog);
参数说明:
如果有多个客户端同时发来连接请求,此时未被服务器处理的连接就会放入连接队列,backlog代表的就是这个全连接队列的最大长度,一般不要设置太大,设置为5或10即可。
返回值:
在初始化中我们将套接字设置为监听套接字。
- bool init()
- {
- //创建套接字
- _sockfd = socket(AF_INET,SOCK_STREAM,0);
- if(_sockfd<0)
- {
- std::cerr << "socket flase" << std::endl;
- return false;
- }
- std::cerr << "socket succes" << std::endl;
- //绑定端口
- struct sockaddr_in local;
- memset(&local,'\0',sizeof(local));
- //协议家族
- local.sin_family=AF_INET;
- //端口号
- local.sin_port=htons(_port);
- //ip
- local.sin_addr.s_addr = inet_addr(_ip.c_str());
-
- //绑定
- if(bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr_in)) < 0 )
- {
- //绑定失败
- std::cerr << "bind error" << std::endl;
- return false;
- }
- std::cerr << "bind succes" << std::endl;
-
- if(listen(_sockfd,5) < 0 )
- {
- //监听失败
- std::cerr << "listen error" << std::endl;
- return false;
- }
- std::cerr << "listen succes" << std::endl;
- }
获取连接
在设置完监听状态后,我们需要接受客户端的连接请求。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
返回值:
-1
并设置全局变量以指示错误类型。- void start()
- {
- while(1)
- {
- struct sockaddr_in sendfrom;
- memset(&sendfrom,'\0',sizeof(sendfrom));
- int client;
- socklen_t len=sizeof(struct sockaddr_in);
- client=accept(_sockfd,(struct sockaddr*)&sendfrom,&len);
- if(client<0)
- {
- std::cerr << "accept error" << std::endl;
- continue;;
- }
- std::string client_ip = inet_ntoa(sendfrom.sin_addr);
- int client_port = ntohs(sendfrom.sin_port);
- std::cout<<"new link->"<<" ["<<client_ip<<"]:"<< client_port <<std::endl;
- server(client);
- }
- }
服务端处理请求
accept返回的是一个描述符套接字·,我们在上一节讲到套接字描述符实际上就是文件描述符,同时文件的读写方式是字节流,而TCP协议的传输也是字节流,所以我们可以使用文件的读写函数接受和发送信息。
使用 read
函数从TCP套接字读取数据:
ssize_t read(int fd, void *buf, size_t count);
参数说明:
返回值说明:
使用 write
函数向TCP套接字写入数据:
ssize_t write(int fd, const void *buf, size_t count);
参数说明:
返回值说明:
- bool server(int sock)
- {
- char buffer[1024];
- while (true)
- {
- ssize_t size = read(sock, buffer, sizeof(buffer)-1);
- if (size > 0)
- { //读取成功
- buffer[size] = '\0';
- std::cout << sock << " [client]:" << buffer << std::endl;
-
- write(sock, buffer, size);
- }
- else if (size == 0)
- { //对端关闭连接
- std::cout << " close!" << std::endl;
- break;
- }
- else
- { //读取失败
- std::cerr << sock << " read error!" << std::endl;
- break;
- }
- }
- close(sock); //归还文件描述符
- }
我们在start中调用,在mian函数中实例化。
- #include"tcp_server.hpp"
-
- int main(int argc,char** argv)
- {
- if(argc<2)
- {
- std::cerr << "port" << std::endl;
- return -1;
- }
-
- //端口号转化
-
- uint16_t port=atoi(argv[1]);
- std::string ip="127.0.0.1";
- Server server(ip,port);
-
- server.init();
- server.start();
-
- return 0;
- }
客户端也与UDP客户端相似,客户端不需要绑定与监听,首先我们创建套接字。
创建套接字
- class Client
- {
- public:
- Client(std::string server_ip , uint16_t server_port)
- :_sockfd(-1)
- ,_server_ip(server_ip)
- ,_server_port(server_port)
- {}
-
- bool init()
- {
- //创建套接字
-
- _sockfd=socket(AF_INET,SOCK_STREAM,0);
- if(_sockfd<0)
- {
- std::cerr << "socket error" <<std::endl;
- return false;
- }
- return true;
- }
-
-
- ~Client()
- {
- if(_sockfd>=0)
- {
- close(_sockfd);
- }
- }
- private:
- uint16_t _server_port;
- std::string _server_ip;
- int _sockfd;
- };
连接服务器
发起连接请求的函数叫做connect,该函数的函数原型如下:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
返回值说明:
- void start()
- {
- char buffer[1024];
- struct sockaddr_in server;
- memset(&server, '\0', sizeof(server));
- //协议家族
- server.sin_family = AF_INET;
- //端口号
- server.sin_port=htons(_server_port);
- //IP
- server.sin_addr.s_addr = inet_addr(_server_ip.c_str());
- socklen_t len=sizeof(server);
- //连接
- if(connect(_sockfd,(struct sockaddr*)&server,len) <0)
- {
- std::cout << "connect false" <<std::endl;
- return;
- }
-
- while (1)
- {
- std::cout << "Please enter# ";
- std::cin.getline(buffer, sizeof(buffer));
- std::string s=buffer;
- write(_sockfd,s.c_str(),s.size());
- }
- }
我们在main中实例化并调用。
- #include"tcp_client.hpp"
-
- int main(int argc,char** argv)
- {
- if(argc < 3)
- {
- std::cout << "name ip port" << std::endl;
- return -1;
- }
- uint16_t port=atoi(argv[2]);
- std::string ip=argv[1];
- Client client(ip,port);
-
- client.init();
- client.start();
-
- return 0;
- }
我们简单测试一下。
可以看到他们成功完成了通讯。