int listen
sockfd:套接字描述符
backlog:已完成连接队列的大小
没有完成三次握手的连接存放在未完成连接队列,已经完成三次握手的连接存放在已完成连接队列,已经完成三次握手的连接等待被accept的连接,或者可以理解为连接状态为”连接建立“的连接。每成功连接一个,backlog++,backlog影响了服务端并发接收连接的能力。
int accept
sockfd:套接字描述符
addr:客户端的地址信息结构(客户端的ip,客户端的端口)
addrlen:客户端地址信息结构的长度
返回值:
成功:返回值是新连接的套接字描述符,文件描述符
失败:-1
int connect
sockfd:套接字描述符
addr:服务端的地址信息结构
服务端的ip
服务端的端口
addrlen:
返回值:
小于0:连接失败
成功:0
ssize_t send
sockfd:套接字描述符
buf:待要发送的数据
flags:
0:阻塞发送
MSG_OOB:发送带外数据
返回值:
大于0:返回发送的字节数量
-1:发送失败
ssize_t recv
sockfd:套接字描述符
accept的返回值,新连接的套接字
buf:将接收的数据放到哪里去
len:buf的最大接收能力
flags:0阻塞接收
返回值:
大于0:正常接收了多少字节数据
等于0:对端将连接关闭了
小于0:接收失败
客户端部分
tcp_client.cpp
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd < 0)
{
perror("listen_sock");
return 0;
}
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(18989);
dest_addr.sin_addr.s_addr = inet_addr("49.232.211.4");
int ret = connect(sockfd, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
if(ret < 0)
{
perror("connect");
return 0;
}
while(1)
{
sleep(1);
char buf[1024] = "i am tcp clients";
ssize_t send_size = send(sockfd, buf, strlen(buf), 0);
if(send_size < 0)
{
perror("send");
continue;
}
memset(buf, '\0', sizeof(buf));
ssize_t recv_size = recv(sockfd, buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
{
perror("recv");
continue;
}
else if(recv_size == 0)
{
printf("server close\n");
close(sockfd);
return 0;
}
printf("server say: %s\n", buf);
}
}
服务器部分
tcp_server.cpp
#include
#include
#include
#include
#include
#include
int main()
{
int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(listen_sock < 0)
{
perror("listen_sock");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(18989);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return 0;
}
ret = listen(listen_sock, 1);
if(ret < 0)
{
perror("listen");
return 0;
}
while(1)
{
struct sockaddr_in peer_addr;
socklen_t peer_addrlen = sizeof(peer_addr);
int new_sock = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addrlen);
if(new_sock < 0)
{
perror("accept");
return 0;
}
printf("accept %s:%d, create new_sock %d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), new_sock);
//recv
char buf[1024] = {0};
ssize_t recv_size = recv(new_sock, buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
{
perror("recv");
continue;
}
else if(recv_size == 0)
{
perror("peer shutdown\n");
close(new_sock);
close(listen_sock);
return 0;
}
printf("client %d say: %s\n", new_sock, buf);
memset(buf, '\0', sizeof(buf));
sprintf(buf, "i am server, hi client %d\n", new_sock);
ssize_t send_size = send(new_sock, buf, strlen(buf), 0);
if(send_size < 0)
{
perror("send");
continue;
}
}
close(listen_sock);
return 0;
}
总结:
在单线程中实现客户端和服务端之间的交互,如果按照如上代码执行,将accept放在while循环的里面,那么每次客户端发送数据之后,只能和服务端交互一次,然后就会阻塞在accept函数,如果将accept放在while循环外面,那么进程就可以和客户端实现多次的数据交互,但是服务端只能和同一个客户端交互,因为永远无法再执行到accept函数。
对于上面这种问题,提出了三种解决方法,方法一,使用多进程实现,方法二,使用多线程实现,方法三,使用高级io中的知识。
tcp_server.cpp
#include
#include
#include
#include
#include
#include
#include
#include
void sigcallback(int signo)
{
wait(NULL);
}
int main()
{
signal(SIGCHLD, sigcallback);
int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(listen_sock < 0)
{
perror("socket");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(18989);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return 0;
}
ret = listen(listen_sock, 1);
if(ret < 0)
{
perror("listen");
return 0;
}
while(1)
{
struct sockaddr_in peer_addr;
socklen_t peer_addrlen = sizeof(peer_addr);
int new_sock = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addrlen);
if(new_sock < 0)
{
perror("accept");
return 0;
}
printf("accept %s:%d, create new_sock %d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), new_sock);
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
close(new_sock);
continue;
}
else if(pid == 0)
{
//child
close(listen_sock);
while(1)
{
char buf[1024] = {0};
ssize_t recv_size = recv(new_sock, buf, strlen(buf) - 1, 0);
if(recv_size < 0)
{
perror("recv");
continue;
}
else if(recv_size == 0)
{
printf("peer shutdown");
close(new_sock);
return 0;
}
printf("client %d say: %s\n", new_sock, buf);
memset(buf, '\0', sizeof(buf));
sprintf(buf, "i am server, hi client %d\n", new_sock);
ssize_t send_size = send(new_sock, buf, strlen(buf), 0);
if(send_size < 0)
{
perror("send");
continue;
}
}
}
else
{
//father
close(new_sock);
continue;
}
}
close(listen_sock);
return 0;
}
使用多进程实现,对于每一个客户端都对应创建一个子进程,子进程用来和客户端的交互,父进程就负责接收经过三次握手的客户端。