int socket(int domain, int type, int protocol);
domain:地址域----不同的网络地址结构 AF_INET即IPv4地址域
type:套接字类型----流式套接字(SOCK_STREAM)/数据报套接字(SOCK_DGRAM)
protocol:使用协议 0表示不同套接字下的默认协议(流式套接字为TCP,数据报套接字为UDP)
IPPROTO_TCP -- TCP协议 IPPROTO_UDP -- UDP协议
返回值:套接字的操作句柄----文件描述符
int bind(int sockfd, struct sockaddr *addr, socklen_t len);
sockfd:创建套接字返回的操作句柄
addr:要绑定的地址信息结构体
len:地址信息的长度
返回值:成功返回0,失败返回-1
告诉操作系统开始接收连接请求
int listen(int sockfd, struct sockaddr *addr, socklen_t len);
backlog参数
ECONNREFUSED
错误信息。在内核版本2.2之前的Linux中,backlog参数是指所有处于半连接状态(SYN_RCVD)和完全连接状态(ESTABLISHED)的socket上限。但自内核版本2.2之后,它只表示处于完全连接状态的socket上限
。backlog的典型值是5
,此时处于完全连接状态的socket最多能有6个(backlog + 1
)。从内核指定socket的pending queue中取出一个socket,返回操作句柄
int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
sockfd:监听套接字----指定要获取 哪个pending queue中的套接字
addr:获取一个套接字,这个套接字与指定的客户端进行通信,通过addr获取这个客户端的地址信息
len:输入输出型参数----指定地址信息想要的长度以及返回实际的地址长度
返回值:成功则返回新获取的套接字的描述符----操作句柄;失败返回-1
接收数据:ssize_t recv(int sockfd, char *buf, int len, int flag);
返回值:成功返回实际读取的数据长度;
连接断开返回0;
读取失败返回-1
(类似管道读取数据:所有写端被关闭,则read返回0)
发送数据:ssize_t send(int sockfd, char *data, int len, int flag);
返回值:成功返回实际发送的数据长度;
失败返回-1
若连接断开触发异常
int close(int fd);
int connect(int sockfd, int sockaddr *addr, socklen_t len);
sockfd:客户端套接字----若还未绑定地址,则操作系统会选择合适的源端地址进行绑定
addr:服务端地址信息----struct sockaddr_in;这个地址信息经过connect之后也会描述到socket中
len:地址信息长度
阻塞
函数:功能是获取新连接,如果当前没有新连接,则阻塞等待直到有新连接阻塞
函数:接收缓冲区没有数据则recv阻塞 / 发送缓冲区满了则send阻塞数据独有
,各自有一份新的通信socket;子进程通过新的socket通信,父进程不需要通信,可以关闭等待子进程退出
,避免产生僵尸进程;为了父进程只负责获取新连接,因此对SIGCHLD
信号自定义处理回调等待#include
#include
#include
#include
#include
#include
#include
#define BACKLOG 10
#define CHECK_RET(q) if((q) == false){return -1;}
class TcpSocket{
public:
TcpSocket():_sockfd(-1){
}
int GetFd(){
return _sockfd;
}
void SetFd(int fd){
_sockfd = fd;
}
// 创建套接字
bool Socket(){
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(_sockfd < 0){
perror("socket error!\n");
return false;
}
return true;
}
void Addr(struct sockaddr_in *addr, const std::string &ip, uint16_t port){
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &(addr->sin_addr.s_addr));
}
// 绑定地址信息
bool Bind(const std::string &ip, const uint16_t port){
// 1.定义IPv4地址结构
struct sockaddr_in addr;
Addr(&addr, ip, port);
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if(ret < 0){
perror("bind error!\n");
return false;
}
return true;
}
// 服务端开始监听
bool Listen(int backlog = BACKLOG){
int ret = listen(_sockfd, backlog);
if(ret < 0){
perror("listen error!\n");
return false;
}
return true;
}
// 客户端发起连接请求
bool Connect(const std::string &ip, const uint16_t port){
// 1. 定义IPv4地址结构,赋予服务端地址信息
struct sockaddr_in addr;
Addr(&addr, ip, port);
// 2. 向服务端发起请求
// 3. connect(客户端描述符, 服务端地址信息, 地址长度)
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(_sockfd, (struct sockaddr*)&addr, len);
if(ret < 0){
perror("connect error!\n");
return false;
}
return true;
}
// 服务端获取新的连接
bool Accept(TcpSocket *sock, std::string *ip = NULL, uint16_t *port = NULL){
// accept(监听套接字, 对端地址信息, 地址信息长度) 返回新的描述符
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
int clisock = accept(_sockfd, (struct sockaddr*)&addr, &len);
if(clisock < 0){
perror("accept error!\n");
return false;
}
sock->_sockfd = clisock;
if(ip != NULL){
*ip = inet_ntoa(addr.sin_addr); // 网络字节序IP->字符串IP
}
if(port != NULL){
*port = ntohs(addr.sin_port);
}
return true;
}
// 发送数据
bool Send(const std::string &data){
int ret = send(_sockfd, data.c_str(), data.size(), 0);
if(ret < 0){
perror("send error!\n");
return false;
}
return true;
}
// 接收数据
bool Recv(std::string *buf){
char tmp[4096] = {
0};
int ret = recv(_sockfd, tmp, 4096, 0);
if(ret < 0){
perror("recv error!\n");
return false;
}else if(ret == 0){
printf("connection break!\n");
return false;
}
buf->assign(tmp, ret); // 从tmp中拷贝ret大小的数据到buf中
return true;
}
// 关闭套接字
void Close(){
close(_sockfd);
_sockfd = -1;
}
private:
int _sockfd;
};
#include
#include "tcpsocket.hpp"
int main(int argc, char *argv[])
{
if(argc != 3){
printf("em : ./tcp_cli 127.0.0.1 9000\n");
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpSocket cli_sock;
CHECK_RET(cli_sock.Socket());
CHECK_RET(cli_sock.Connect(ip, port));
while(1){
printf("client say : ");
fflush(stdout);
std::string buf;
std::cin >> buf;
CHECK_RET(cli_sock.Send(buf));
buf.clear();
CHECK_RET(cli_sock.Recv(&buf));
printf("server say : %s\n", buf.c_str());
}
cli_sock.Close();
return 0;
}
#include
#include
#include
#include "tcpsocket.hpp"
void *thr_start(void *arg){
long fd = (long)arg;
TcpSocket cli_sock;
cli_sock.SetFd(fd);
while(1){
std::string buf;
if(cli_sock.Recv(&buf) == false){
cli_sock.Close();
pthread_exit(NULL);
continue;
}
printf("client say : %s\n", &buf[0]);
std::cout << "server say : ";
fflush(stdout);
buf.clear();
std::cin >> buf;
if(cli_sock.Send(buf) == false){
cli_sock.Close();
pthread_exit(NULL);
}
}
cli_sock.Close();
return NULL;
}
int main(int argc, char *argv[])
{
if(argc != 3){
printf("em : ./tcp_srv 127.0.0.1 9000\n");
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpSocket lst_sock;
CHECK_RET(lst_sock.Socket());
CHECK_RET(lst_sock.Bind(ip, port));
CHECK_RET(lst_sock.Listen());
while(1){
TcpSocket cli_sock;
std::string cli_ip;
uint16_t cli_port;
bool ret = lst_sock.Accept(&cli_sock, &cli_ip, &cli_port);
if(ret == false){
continue;
}
printf("client : [%s : %d]", &cli_ip[0], cli_port);
//---------------------------------------------
pthread_t tid;
// cli_sock是一个局部变量--循环完了这个资源就会被释放
pthread_create(&tid, NULL, thr_start, (void*)cli_sock.GetFd());
pthread_detach(tid); // 不关心线程返回值,分离线程,退出后自动释放资源
// 主线程不能关闭cli_sock,因为多线程是共用描述符的
}
lst_sock.Close();
return 0;
}
一致的
连接断开在发送端与接收端上的表现:
返回0
;反之若recv返回0,表示的就是连接断开SIGPIPE
,导致进程退出管道:
SIGPIPE
返回0
客户端:socket / sendto / recvfrom / close
服务端:socket / bind / recvfrom / sendto / close
客户端:socket / connect / send / recv / close
服务端:socket / bind / listen / accept / recv / send / close