tcp 通信 看了必须有点东西
Posted 程序字母K
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tcp 通信 看了必须有点东西相关的知识,希望对你有一定的参考价值。
大体预览
- 从tcp的流程——接口——代码编写
- 服务端流程(server)(文字说明):
- 客户端流程(client)(文字说明):
- 接口实现;
- 写一个`tcpsocket.hpp`的类,用于封装tcp接口(代码);
- 调用`tcpsocket.hpp`类,写tcp通信程序服务端`tcp_csrv.cpp`(代码);
- 调用`tcpsocket.hpp`类,写tcp通信程序客户端`tcp_cli.cpp`(代码);
- 本质原因(文字说明):
- 解决方案(文字说明):
- 多线程::调用的`tcpsocket.hpp`类,写的服务端`thread_srv.cpp`(代码);
- 多进程::调用的`tcpsocket.hpp`类,写的客户单`process_srv.cpp`(代码);
从tcp的流程——接口——代码编写
服务端流程(server)(文字说明):
- 创建套接字;
- 为套接字绑定地址信息;
- 开始监听:
告诉操作系统可以开始处理客户单的连接请求;
系统会为每一个客户端创建一个新的套接字;
- 获取新连接;
- 收发数据(使用新建套接字);
- 关闭套接字;
客户端流程(client)(文字说明):
- 创建套接字;
- 为套接字绑定地址信息;
客户端,不推荐主动绑定,端口可能被占用等问题,不绑定系统会选择合适的绑定;
- 向服务端发送连接请求;
- 收发收据;
- 关闭套接字;
接口实现;
1.创建套接字:
int socket(int domain,int type,int protocol);
2.绑定地址信息:int bind(int sockfd,struct sockaddr* addr,socklen len);
3.开始监听:int listen(int sockfd,int backlog);
backlog:服务端能够在同一时间处理的最大连接数;
已完成连接队列的节点数量=backlog+1;
返回值:成功返回0;失败返回-1;
4.服务端获取新建连接:int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
sockfd:监听套接字–服务端最早创建的套接字-只用与获取新建连接;
cliaddr:新的连接的客户端地址信息;
addrlen:输入输出参数,指定地址信息长度,以及返回实际长度;
返回值:新建连接的描述符–后面用于和客户端通信;
5.收发数据:tcp通信数据中包含了五元组,因此不需要指定地址;
ssize_t send(int sockfd,void *data,int len,int flag);
返回值:成功返回实际发送长度,失败返回-1;连接断开会触发异常;
6.关闭套接字:int close(fd);
写一个tcpsocket.hpp
的类,用于封装tcp接口(代码);
#include <cstdio>
#include <iostream>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define CHECK_RET(q) if((q)==false){return -1;}
#define LISTEN_BACKLOG 5
class TcpSocket{
private:
int _sockfd;
public:
TcpSocket():_sockfd(-1){}
bool Socket() {
_sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (_sockfd < 0) {
perror("socket error");
return false;
}
return true;
}
bool Bind(const std::string &ip, const uint16_t port){
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(&ip[0]);
socklen_t len = sizeof(sockaddr_in);
int ret = bind(_sockfd, (sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
return false;
}
return true;
}
bool Listen(int backlog = LISTEN_BACKLOG) {
//listen(描述符,同一时间连接数)
int ret = listen(_sockfd, backlog);
if (ret < 0) {
perror("listen error");
return false;
}
return true;
}
bool Connect(const std::string &ip,const int port) {
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(&ip[0]);
socklen_t len = sizeof(sockaddr_in);
//connect(描述符,服务端地址, 地址长度)
int ret = connect(_sockfd, (sockaddr*)&addr, len);
if (ret < 0) {
perror("connect error");
return false;
}
return true;
}
bool Accept(TcpSocket *sock, std::string *ip = NULL,
uint16_t *port = NULL) {
//int accept(监听套接字, 回去客户端地址, 长度)
sockaddr_in addr;
socklen_t len = sizeof(sockaddr_in);
int newfd = accept(_sockfd,(sockaddr*)&addr,&len);
if (newfd < 0) {
perror("accept error");
return false;
}
sock->_sockfd = newfd;
if (ip != NULL) {
*ip = inet_ntoa(addr.sin_addr);
}
if (port != NULL) {
*port = ntohs(addr.sin_port);
}
return true;
}
bool Recv(std::string *buf) {
//int recv(描述符,空间,数据长度,标志位)
//返回值:实际获取大小, 0-连接断开; -1-出错了
char tmp[4096] = {0};
int ret = recv(_sockfd, tmp, 4096, 0);
if (ret < 0) {
perror("recv error");
return false;
}else if (ret == 0) {
printf("peer shutdown");
return false;
}
buf->assign(tmp, ret);
return true;
}
bool Send(const std::string &data) {
//int send(描述符,数据,长度,标志位)
int total = 0;
while(total < data.size()) {
int ret = send(_sockfd, &data[0] + total,
data.size() - total, 0);
if (ret < 0) {
perror("send error");
return false;
}
total += ret;
}
return true;
}
bool Close() {
if (_sockfd != -1) {
close(_sockfd);
}
return true;
}
};
调用tcpsocket.hpp
类,写tcp通信程序服务端tcp_csrv.cpp
(代码);
#include "tcpsocket.hpp"
int main(int argc, char *argv[])
{
//通过程序运行参数指定服务端要绑定的地址
// ./tcp_srv 192.168.2.2 9000
if (argc != 3) {
printf("usage: ./tcp_src 192.168.2.2 9000\\n");
return -1;
}
std::string srvip = argv[1];
uint16_t srvport = std::stoi(argv[2]);
TcpSocket lst_sock;//监听套接字
//1. 创建套接字
CHECK_RET(lst_sock.Socket());
//2. 绑定地址信息
CHECK_RET(lst_sock.Bind(srvip, srvport));
//3. 开始监听
CHECK_RET(lst_sock.Listen());
while(1) {
//4. 获取新建连接
TcpSocket clisock;
std::string cliip;
uint16_t cliport;
bool ret = lst_sock.Accept(&clisock, &cliip,&cliport);
if (ret == false) {
continue;
}
std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\\n";
//5. 收发数据--使用获取的新建套接字进行通信
std::string buf;
ret = clisock.Recv(&buf);
if (ret == false) {
clisock.Close();
continue;
}
std::cout << "client say: " << buf << std::endl;
buf.clear();
std::cout << "server say: ";
std::cin >> buf;
ret = clisock.Send(buf);
if (ret == false) {
clisock.Close();
}
}
//6. 关闭套接字
lst_sock.Close();
}
调用tcpsocket.hpp
类,写tcp通信程序客户端tcp_cli.cpp
(代码);
#include "tcpsocket.hpp"
int main(int argc, char *argv[])
{
//通过参数传入要连接的服务端的地址信息
if (argc != 3) {
printf("usage: ./tcp_cli srvip srvport\\n");
return -1;
}
std::string srvip = argv[1];
uint16_t srvport = std::stoi(argv[2]);
TcpSocket cli_sock;
//1. 创建套接字
CHECK_RET(cli_sock.Socket());
//2. 绑定地址信息(不推荐)
//3. 向服务端发起连接
CHECK_RET(cli_sock.Connect(srvip, srvport));
while(1) {
//4. 收发数据
std::string buf;
std::cout << "client say: ";
std::cin >> buf;
CHECK_RET(cli_sock.Send(buf));
buf.clear();
CHECK_RET(cli_sock.Recv(&buf));
std::cout << "server say: " << buf << std::endl;
}
//5. 关闭套接字
CHECK_RET(cli_sock.Close());
return 0;
}
上面流程出现问题:
accept接口和recv接口都是阻塞接口,任意一个接口的调用,都可能导致服务端流程的阻塞;
本质原因(文字说明):
当前服务端不知道什么时候会有新连接到来,什么时候收到数据,只能固定去调用接口,但是这种会导致阻塞;
解决方案(文字说明):
多执行流并发处理,为每一个客户端都创建一个执行流负责与这个客户端进行通信;
**好处:**
1.主线程卡在获取新建连接这里,但是不影响客户端的通信;
2.某个客户端的通信阻塞,也不会影响主线程以及其他线程;
在主线程中,获取新建连接,一旦获取到则创建一个执行流,通过这个创建连接与客户进行通信;
多线程(文字说明):
普通线程与主线程数据共享,指定入口函数执行;
主线程不能随意释放套接字,因为数据共享。一旦释放其他线程无法使用;
多进程(文字说明):
子进程复制了父进程,但是数据独有
1.注意僵尸进程;
2.注意父子进程数据各自独有,父进程用不到新建套接字因此创建子进程之后直接释放掉父进程的套接字,否则会造成资源泄露;
多线程::调用的tcpsocket.hpp
类,写的服务端thread_srv.cpp
(代码);
#include "tcpsocket.hpp"
#include <pthread.h>
void *thr_entry(void *arg)
{
bool ret;
TcpSocket *clisock = (TcpSocket*)arg;
while(1) {
//5. 收发数据--使用获取的新建套接字进行通信
std::string buf;
ret = clisock->Recv(&buf);
if (ret == false) {
clisock->Close();
delete clisock;
return NULL;
}
std::cout << "client say: " << buf << std::endl;
buf.clear();
std::cout << "server say: ";
std::cin >> buf;
ret = clisock->Send(buf);
if (ret == false) {
clisock->Close();
delete clisock;
return NULL;
}
}
clisock->Close();
delete clisock;
return NULL;
}
int main(int argc, char *argv[])
{
//通过程序运行参数指定服务端要绑定的地址
// ./tcp_srv 192.168.2.2 9000
if (argc != 3) {
printf("usage: ./tcp_src 192.168.2.2 9000\\n");
return -1;
}
std::string srvip = argv[1];
uint16_t srvport = std::stoi(argv[2]);
TcpSocket lst_sock;//监听套接字
//1. 创建套接字
CHECK_RET(lst_sock.Socket());
//2. 绑定地址信息
CHECK_RET(lst_sock.Bind(srvip, srvport));
//3. 开始监听
CHECK_RET(lst_sock.Listen());
while(1) {
//4. 获取新建连接
TcpSocket *clisock = new TcpSocket();
std::string cliip;
uint16_t cliport;
bool ret = lst_sock.Accept(clisock, &cliip,&cliport);
if (ret == false) {
continue;
}
std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\\n";
//创建线程专门负责与指定客户端的通信
pthread_t tid;
pthread_create(&tid, NULL, thr_entry, (void*)clisock);
pthread_detach(tid);
}
//6. 关闭套接字
lst_sock.Close();
}
多进程::调用的tcpsocket.hpp
类,写的客户单process_srv.cpp
(代码);
#include "tcpsocket.hpp"
#include <signal.h>
#include <sys/wait.h>
void sigcb(int no)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}
void worker(TcpSocket &clisock)
{ //child process
bool ret;
while(1) {
//5. 收发数据--使用获取的新建套接字进行通信
std::string buf;
ret = clisock.Recv(&buf);
if (ret == false) {
clisock.Close();
exit(0);
}
std::cout <<"client say: "<<buf<<std::endl;
buf.clear();
std::cout << "server say: ";
std::cin >> buf;
ret = clisock.Send(buf);
if (ret == false) {
clisock.Close();
exit(0);
}
}
clisock.Close();//释放的是子进程的clisock
exit(0);
return;
}
int main(int argc, char *argv[])
{
//通过程序运行参数指定服务端要绑定的地址
// ./tcp_srv 192.168.2.2 9000
if (argc != 3) {
printf("usage: ./tcp_src 192.168.2.2 9000\\n");
return -1;
}
signal(SIGCHLD, SIG_IGN);
//signal(SIGCHLD, sigcb);
std::string srvip = argv[1]以上是关于tcp 通信 看了必须有点东西的主要内容,如果未能解决你的问题,请参考以下文章