目录
[TCP编程概述]
1.TCP通信流程
客户端:主动连接服务器,和服务器进行通信
服务器:被动连接客户端,启动新的进程或者线程,通过新的端口号服务客户端(并发服务器)
[TCP客户端]
1.TCP套接字的创建
1.1定义一个套接字
int socket_fd = socket(AF_INET,SOCK_STREAM,0);
1.2使用connect建立和服务器套接字连接
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能
使当前套接字能够建立和服务器的连接(需要知道服务器的IP 端口)
参数
sockfd:想要连接的套接字
addr:目标服务器的地址结构体地址
addrlen:目标结构体地址大小
返回值
成功:0
失败:-1,并且会设置错误值
1.3定义结构体后使用conne建立连接
这里的客户端并没有绑定,所以端口是随机分配的
int socket_fd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ser_addr;
//定义在栈区,清零结构体
bzero(&ser_addr, sizeof(ser_addr));
//设置地址族为IPv4
ser_addr.sin_family = AF_INET;
//设置端口的值,主机字节序转网络字节序
ser_addr.sin_port = htons (8000 );
//设置目的主机的IP,点分十进制串转32无符号
ser_addr.sin_addr.s_addr = inet_addr( "xx.xx.xx.xxx");
//建立连接
connect(sockfd,(struct sockaddr *)&ser_addr, sizeof(ser_addr));
1.4send发送数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能
向建立好连接的服务器发送指定数据
参数
sockfd:套接字
buf:发送数据缓冲区
len:发送数据长度
flags:默认为0
返回值
成功:返回发送字节数
失败:-1
TCP不能想UDP一样发送0长度报文,因为发送0长度报文是断开连接的意思
1.5recv接收信息(默认阻塞)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能
建立连接后,从服务器接受信息
参数
sockfd:套接字
buf:接收数据缓冲区
len:接受数据长度
flags:默认0
返回值
成功:接收到的字节个数
失败:-1
recv如果接受到0长度报文,则对方已经断开连接
2.TCP客户端收发数据
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//创建TCP套接字
int socket_fd = socket(AF_INET,SOCK_STREAM,0);
//创建目标地址结构体
struct sockaddr_in dst_addr;
bzero(&dst_addr,sizeof(dst_addr));
dst_addr.sin_family = AF_INET;
dst_addr.sin_port = htons(8000);
//inet_addr只支持IPv4
//inet_pton支持IPv4和IPv6
//服务器的IP地址
dst_addr.sin_addr.s_addr = inet_addr("XXX.XXX.XXX.XXX");
//建立客户端和服务器的连接
connect(socket_fd,(struct sockaddr *)&dst_addr,sizeof(dst_addr));
//通过TCP给服务器发送信息
int send_len = send(socket_fd,"hello tcp",strlen("hello tcp"),0);
printf("信息发送成功,发送了%d字节\n",send_len);
//接受信息(带阻塞)
char buf[1000] = "";
int recv_len = recv(socket_fd,buf,sizeof(buf),0);
printf("成功接收到如下信息%s,接收字节为%d",buf,recv_len);
close(socket_fd);
return 0;
}
[TCP服务器]
1.作为服务器的条件
如果想要编写一个服务器,那么应该遵循如下步骤,首先,需要创建一个套接字,然后使用bind对其进行IP端口的绑定,因为作为服务器,如果没有确切的IP和端口,其他客户端是无法连接你的。当绑定完毕后,使用accept对客户端连接列表中的客户端进行提取,然后创建一个线程或者进程与客户端进行通信。
2.监听套接字
服务器需要不断循环监听是否有新的客户端连接进来,这时候就需要一个监听套接字来完成这个任务。使用listen可以把一个套接字变为监听套接字
3.accept提取客户端(默认阻塞)
当一个客户端连接了一个服务器后,会在连接列表中的未完成部分,当完成了三次握手之后,accept可以成功的把客户端提取出来,然后创建一个临时的套接字与之进行通信。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能
从连接列表中提取完成三次握手的客户端
参数
sockfd:监听套接字
addr:客户端地址信息
addrlen:地址结构体长度
返回值
成功:与客户端进行临时通信的套接字,代表服务器真正和客户端的连接端点
失败:-1
4.编写一个TCP服务器
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//创建监听套接字
int listen_socket = socket(AF_INET,SOCK_STREAM,0);
//创建地址结构体
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(1145);
//客户端IP
server_addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx");
//绑定
int bind_res = bind(listen_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
if( bind_res < 0 )
{
perror("bind:");
}
//设置套接字为监听套接字
listen(listen_socket,10);
//提取连接此服务器的地址
struct sockaddr_in client_addr;
socklen_t client_addr_len;
bzero(&client_addr,sizeof(client_addr));
//提取在连接列标中完成三次握手的ip
char clinet_ip[16] = "";
int client_socket = accept(listen_socket,(struct sockaddr *)&client_addr,&client_addr_len);
printf("客户端%s端口为%hu连接成功,使用套接字%hu与之通信\n",\
inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,clinet_ip,16),\
ntohs(client_addr.sin_port),ntohs(client_socket));
//客户端接受数据
char recv_data[128] = "";
recv(client_socket,recv_data,sizeof(recv_data),0);
printf("服务器接收到数据%s\n",recv_data);
//服务器给客户端通过临时端口发送数据
send(client_socket,"hello client",strlen("hello client"),0);
printf("发送数据成功!!\n");
while(1);
//关闭监听套接字
close(listen_socket);
return 0;
}
[close关闭套接字]
1.当客户端使用close
客户端使用close关闭套接字,会断开当前连接,导致服务器收到一个0长度报文(可以作为服务器不在继续接受数据的依据)
2.当服务器使用close
如果服务器close了监听的套接字,那么该服务器将无法在继续监听新的客户端的到来,但是不会影响现有的连接
如果服务器close了通信的套接字,那么会断开与客户端的连接,但是不会影响监听
[三次握手四次挥手]
详见博主的另外一篇文章
https://blog.csdn.net/m0_72372635/article/details/131617634?spm=1001.2014.3001.5501
[基于多进程的并发服务器]
int main(int argc, char *argv [])
{
//创建套接字sockfd
//绑定(bind)套接字
//sockfd监听(listen)套接字sockfd
while(1)
{
//阻塞,直到有客户端完成三次握手完成连接
//提取临时通信的套接字
int temp_socket = accept(.....);
pid_t pid = fork();
//子进程
if( 0 == pid )
{
//关闭监听套接字
close(sockfd);
//实现函数
fun();
//关闭临时套接字
close(temp_socket);
exit(-1);
}
else if( pid > 0 )
{
//关闭临时套接字
close(temp_socket);
}
}
close(sockfd);
return 0;
}
多进程并发服务器实现
这里实现了一个基于多进程的高并发服务器,当客户端连接的时候,会打印IP地址以及端口,并且客户端发送的内容服务器会提取之后发送回去(可能回收机制有点缺陷)
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <unistd.h>
#include <sys/types.h>
void Recv_ClientMsg(int socket)
{
while(1)
{
char recv_data[128] = "";
//接受信息
recv(socket,recv_data,128,0);
send(socket,recv_data,128,0);
if( 0 == strcmp("exit connect",recv_data) )
break;
}
}
int main(int argc, char const *argv[])
{
//创建套接字
int listen_socket = socket(AF_INET,SOCK_STREAM,0);
//绑定
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx");
int bind_res = bind(listen_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
if( bind_res != 0 )
{
perror("bind:");
}
//设定服务器的套接字为监听套接字
listen(listen_socket,10);
//接受来访客户端IP 端口
struct sockaddr_in client_addr;
socklen_t client_addr_len;
while(1)
{
int client_socket = accept(listen_socket,(struct sockaddr *)&client_addr,&client_addr_len);
//每次有客户端连接进入,都创建进程
pid_t pid = fork();
//子进程
if( 0 == pid )
{
//查看哪个IP到来
char ip[16] = "";
inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,16);
printf("%s进入连接,端口%hu\n",ip,ntohs(client_addr.sin_port));
//子进程中监听套接字无用
close(listen_socket);
Recv_ClientMsg(client_socket);
printf("%s断开连接,端口%hu\n",ip,ntohs(client_addr.sin_port));
//关闭临时套接字
close(client_socket);
_exit(-1);
}
//父进程
else if( pid > 0 )
{
//父进程中临时套接字无用
close(client_socket);
}
}
close(listen_socket);
return 0;
}
[基于多线程的并发服务器]
int main(int argc, char *argv [])
{
//创建套接字sockfd
//绑定(bind)套接字
//sockfd监听(listen)套接字sockfd
while(1)
{
//阻塞,直到有客户端完成三次握手完成连接
//提取临时通信的套接字
int temp_socket = accept(.....);
//每次提取到新的客户端,创建线程
pthread_t tid;
pthread_create(&tid,NULL,client_fun,temp_socket );
//分离线程
pthread_detach(tid);
}
close(sockfd);
return 0;
}
void* client_fun(void* arg)
{
//接受传过来的套接字
int temp_socket = (int)arg;
//完成任务
//关闭套接字
close(temp_socket );
return NULL;
}
多线程并发服务器实现
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
typedef struct
{
int client_socket;
struct sockaddr_in client_addr;
}ClientMsg;
void* client_pthread_fun(void* arg)
{
ClientMsg client_msg = *(ClientMsg*)arg;
char ip[16] = "";
char recv_data[128] = "";
inet_ntop(AF_INET,&client_msg.client_addr.sin_addr.s_addr,ip,16);
printf("%s进入连接,端口%hu\n",ip,ntohs(client_msg.client_addr.sin_port));
while(1)
{
int len = recv(client_msg.client_socket,recv_data,sizeof(recv_data),0);
//判断退出条件
if( len <= 0 )
{
printf("%s退出连接,端口%hu\n",ip,ntohs(client_msg.client_addr.sin_port));
close(client_msg.client_socket);
break;
}
send(client_msg.client_socket,recv_data,strlen(recv_data),0);
printf("往%s端口%hu发送了%s\n",ip,ntohs(client_msg.client_addr.sin_port),recv_data);
}
pthread_exit(NULL);
return NULL;
}
int main(int argc, char const *argv[])
{
//创建套接字
int listen_socket = socket(AF_INET,SOCK_STREAM,0);
//设置套接字允许端口复用
int option = 1;
setsockopt(listen_socket,SOL_SOCKET,SO_REUSEADDR,&option,sizeof(option));
//绑定套接字
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx");
int bind_res = bind(listen_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
if( bind_res != 0 )
{
perror("bind");
close(listen_socket);
return 0;
}
//设置为监听套接字
listen(listen_socket,10);
//创建客户端地址接受结构体
struct sockaddr_in client_addr;
socklen_t client_addr_len;
//创建结构体可以传参到线程
ClientMsg client_msg;
//不断循环提取完成三次握手的客户端
while(1)
{
//提取客户端
int connect_socket = accept(listen_socket,(struct sockaddr *)&client_addr,&client_addr_len);
//查看哪个IP到来
client_msg.client_socket = connect_socket;
client_msg.client_addr.sin_port = ntohs(client_addr.sin_port);
client_msg.client_addr.sin_addr.s_addr = client_addr.sin_addr.s_addr;
//创建线程
pthread_t client_pthread;
pthread_create(&client_pthread,NULL,client_pthread_fun,&client_msg);
//设置线程分离
pthread_detach(client_pthread);
}
close(listen_socket);
return 0;
}