套接字编程4(tcp)
Posted 李憨憨_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了套接字编程4(tcp)相关的知识,希望对你有一定的参考价值。
用类实例化对象
1.创建套接字
bool Socket(){
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(_sockfd < 0){
perror("socket error");
return false;
}
return true;
}
2.绑定地址信息
bool Bind(const 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);
int ret = bind(_sockfd, (sockaddr*)&addr, len);
if(ret < 0){
perror("bind error");
return false;
}
return true;
}
3.监听套接字
bool Listen(int backlog = LISTEN_BACKLOG){
//listen(描述符,同一时间连接数)
int ret = listen(_sockfd, backlog);
if(ret < 0){
perror("listen error");
return false;
}
return true;
}
4.新建连接
bool Connect(const 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);
//connect(描述符, 服务端地址, 地址长度)
int ret = connect(_sockfd, (sockaddr*)&addr, len);
if(ret < 0){
perror("connect error");
return false;
}
return true;
}
5.获取新建连接
bool Accept(TcpSocket *sock, 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("newfd 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;
}
6.接收数据
bool Recv(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;
}
7.发送数据
bool Send(const 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;
}
8.关闭套接字
bool Close(){
if(_sockfd != -1){
close(_sockfd);
}
return true;
}
服务端
#include "tcpsocket.hpp"
using namespace std;
int main(int argc, char *argv[]){
//通过程序运行参数指定服务端要绑定的地址
//./tcp_srv 192.168.2.2 9000
if(argc != 3){
printf("usage: ./tcp_srv 192.168.2.2 9000\\n");
return -1;
}
string srvip = argv[1];
uint16_t srvport = 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 cli_sock;
string cli_ip;
uint16_t cli_port;
bool ret = lst_sock.Accept(&cli_sock, &cli_ip, &cli_port);
if(ret == false){
continue;
}
cout << "get new conn: " << cli_ip << "-" << cli_port << endl;
//5.收发数据--使用获取的新建套接字进行通信
string buf;
ret = cli_sock.Recv(&buf);
if(ret == false){
cli_sock.Close();
continue;
}
cout << "client say: " << buf << endl;
buf.clear();
cout << "server say: ";
cin >> buf;
ret = cli_sock.Send(buf);
if(ret == false){
cli_sock.Close();
}
}
//6.关闭套接字
lst_sock.Close();
}
客户端
#include "tcpstocket.hpp"
using namespace std;
int main(int argc, char *srgv){
//通过参数传入要连接的服务端的地址信息
if(argc != 3){
printf("usage ./tcp_cli srvip srvport\\n");
return -1;
}
string srvip = srgv[1];
uint16_t srvport = stoi(argv[2]);
TcpSocket cli_sock;
//1.创建套接字
CHECK_RET(cli_sock.Socket());
//2.绑定地址信息
//3.向服务端发起连接
CHECK_RET(cli_sock.Connect(srvip, srvport));
while(1){
//4.收发数据
string buf;
cout << "client say: ";
cin >> buf;
CHECK_RET(cli_sock.Send(buf));
buf.clear();
CHECK_RET(cli_sock.Recv(&buf));
cout << "server say: " << buf << endl;
}
//5.关闭套接字
CHECK_RET(cli_sock.Close());
return 0;
}
存在问题
这个流程里面存在一个问题:
当我们现在有一个客户端, 这个客户端说我给你发送一个请求过来了(开始监听之后我们就可以发送请求了), 连接请求一过来, 这个连接的建立在内核里面就完成了. 而我们accept(获取新建连接)接口也是一个阻塞接口(如果有新建连接我就获取, 如果没有我就等着). 所以这时候我们新建连接建立成功了之后, 然后使用新连接接收数据, 使用新连接发送数据, 走到这一步之后需要注意第一次循环结束了.
它走上来又要获取新建连接, 这时候这个客户端已经连接成功了, 但是程序因为走了一圈, 给你通信了一次之后, 走到获取新建连接这一步了. 但是这个时候就算你这个客户端给我发送数据, 我是走不下去去接收的, 也走不下去去发送, 因为我的程序流程就走到获取新建连接这里不动了, 我在等待下一个新的客户端. 所以我们再穿件一个客户端又能通信一次, 第二个客户端也卡在了新建连接又不动了.
所以处理这个问题的本质是什么?
accpet与recv以及send都是阻塞接口, 任意一个接口的调用, 都有可能导致服务端流程阻塞.
本质原因: 当前的服务端, 因为不知道什么时候有新连接到来, 什么时候哪个客户端有数据到来, 因此流程只能固定的去调用接口, 但是这种调用方式有可能会造成阻塞
解决方案
多执行流并发处理
为每个客户端都创建一个执行流负责与这个客户端进行通信
好处:
1.主线程卡在获取新建连接这里, 但是不影响客户端的通信
2,某个客户端的通信阻塞, 也不会影响主线程以及其他线程
在主线程中, 获取新建连接,一旦获取到了则创建一个执行流, 通过这个新建连接与客户端进行通信.
多线程: 普通线程与主线程数据共享, 指定入口函数执行
主线程不能随意释放套接字, 因为资源共享, 一旦释放其他线程无法使用.
多进程: 子进程复制了父进程, 但是数据独有.
1.注意僵尸进程的处理.
2.注意父子进程数据独有, 父进程用不到新建套接字因此创建子进程之后直接释放掉, 否则会造成资源泄露.
以上是关于套接字编程4(tcp)的主要内容,如果未能解决你的问题,请参考以下文章