文章目录
摘要:这篇文章重点是实操,基本概念讲解少,通过搭建服务端和客户端来进行简单的通信,多进程服务器,多线程服务器。
关键字:TCP,TCP涉及C语言部分相关函数讲解,基于TCP协议实现多进程服务器和多线程服务器。
1. TCP简介
TCP是网络传输层的协议,具有传输稳定,数据准确的特点。
这篇文章概念上不多讲,直接进行客户端和服务器实战练习。
1.TCP/IP传输协议,易懂!!!(这篇文章可以从了解网络各层中,了解TCP传输协议)
2.TCP三次握手,四次挥手,通俗易懂!!!(这篇中深入了解TCP数据传输稳定和数据准确的特点)
2. 搭建框图
服务端和客户端的搭建框图。
3. 相关函数介绍
3.1 socket函数
任务:搭建通信平台
int socket(int domain,int type,int protocol);
参数
domain
AF_INET
AF_INET6
AF_UNIX,AF_LOCAL
AF_NETLINK
AF_PACKET
type
SOCK_STREAM: 流式套接字,唯一对应于TCP
SOCK_DGRAM:数据报套接字,唯一对应着UDP
SOCK_RAW:原始套接字
protocol
一般填0,原始套接字编程时需填充
返回值
成功返回文件描述符
出错返回-1
如果是IPV6编程,要使用struct sockddr_in6结构体(man 7 IPV6),通常使用struct sockaddr_storage来编程。
3.2 bind函数
任务:绑定IP和端口
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
参数
sockfd:通过socket()函数拿到的fd
addr:采用struct sockaddr的结构体地址,通用结构体
struct sockaddr{
sa_family_t sa_family;
char sa_data[4];
}
struct sockaddr_in{ 基于Internel通信结构体
as_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
sin_zero; //填充字节,需清零
}
struct in_addr{
uint32_t s_addr;
}
addrlen:地址长度
3.3 listen函数
任务:设置队列长度,设置被动socket,能接收连接请求
int listen(int sockfd,int backlog);
参数:
sockfd: 通过socket()函数拿到的fd;
backLog:同时允许几路客户端和服务器进行正在连接的过程(正在三次握手),一般填5。
内核中服务器的套接字fd会维护2个链表
1.正在三次握手的客户端链表(数量=2*backlog+1)
2.已经建立好连接的客户端链表(已经完成三次握手分配好了的newfd)
返回值:
成功返回0
出错返回-1
1.backlog:内核队列的长度,在Linux2.2以前指未完成队列和已完成队列的长度之和,而在Linux2.2以后指已完成队列长度。
下面验证版本backlog队列。
1.1我使用参考代码运行程序
1.2 文章博主运行结果
代码参考文章:
listen()函数中backlog参数分析
我看了好几篇文章都有不同的见解,大家想看的可以看看:
listen函数backlog参数的一点探讨
TCP 的backlog详解及半连接队列和全连接队列
3.4 accept函数
任务:阻塞等待连接,返回一个新套接字
我之前认为三次握手是在accept函数完成,而是在内核中完成,accept只是负责在内核全连接队列中取出一个。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
sockfd:经过前面socket()创建并通过bind(),listen()设置过的fd
addr:指向存放地址信息的结构体的首地址
获取客户端IP地址和端口号
addrlen:存放地址信息的结构体的大小
返回值
成功,返回返回已经建立连接的新的newfd
出错,返回-1
3.5 connect函数
客户端连接服务器
int connect (int sockfd, struct sockaddr * serv_addr, int addrlen)
参数:
sockfd:通过socket()函数拿到的fd
addr:struct sockaddr的结构体变量地址
addrlen:地址长度
返回值:
成功,返回0
失败,返回-1
3.6 send函数
跟write效果一样
ssize_t send(int sockfd,
const void *buf,
size_t len,
int flags
);
参数:
返回值:
sockfd:socket函数返回的fd
buffer:发送缓冲区首地址
length:发送的字节
f
lags:发送方式(通常为0),作用和write一样
MSG_DONTWAIT,非阻塞
MSG_OOB:用于TCP类型的带外数据(out of band)
成功:实际发送的字节数
失败:-1,并设置errno
3.7 recv函数
跟read同效
int recv( SOCKET s, char FAR *buf, int len, int flags);
lag:一般填0,和read作用一样
特殊的标志:
3.UDP编程
MSG_DONTWAIT
MSG_OOB:读取带外数据
MSG_PEEK:流
3.8 其他函数
还有recvfrom 和 sendto函数用的不多,这里不多做讲解。
关于IP和端口号主机字节序和网络字节序之间的转换函数在另一篇文章中讲解:
TCP/IP传输协议,易懂!!!
4. 实战
4.1 一对一模型
4.1.1 server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.152"
int main()
{
int sockfd;
//1.socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd==-1)
{
perror("socket");
return -1;
}
//2.bind
//2.1 handling protocol
struct sockaddr_in sin,cin;
bzero(&sin, sizeof(sin));
bzero(&cin, sizeof(cin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
if(bind(sockfd,(struct sockaddr*)&sin, sizeof(sin))==-1)
{
perror("bind");
return -2;
}
//3.listen
if(listen(sockfd, 5)==-1)
{
perror("listen");
return -3;
}
sleep(60);
int newfd = 0;
pid_t pid;
socklen_t addrlen = sizeof(cin);
while(1)
{
//4.accept
printf("connecting......\n");
newfd = accept(sockfd, (struct sockaddr*)&cin, &addrlen);
printf("newfd:%d\n", newfd);
if(newfd==-1)
{
perror("accept");
return -4;
}
printf("child newfd:%d\n", newfd);
printf("succeed to connect client, cin.sin_port:%d\n", ntohs(cin.sin_port));
char readBuf[BUF_SIZE] = {0};
while(1)
{
//5.handling data;
recv(newfd, readBuf, BUF_SIZE, 0);
printf("the server accepts the content from the client is:\n");
printf("%s\n",readBuf);
if(!strncasecmp(readBuf, QUIT_FLAG, strlen(QUIT_FLAG)))
{
printf("the exited\n");
close(newfd);
break;
}
bzero(readBuf, BUF_SIZE);
}
}
close(sockfd);
return 0;
}
4.1.2 client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.151"
int main()
{
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
if(inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr.s_addr)<=0)
{
perror("inet_pton");
exit(1);
}
if(connect(sockfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
{
perror("connect");
return -2;
}
char writeBuf[BUF_SIZE] = {0};
while(1)
{
printf("====input===\n");
fgets(writeBuf, BUF_SIZE-1, stdin);
if(send(sockfd, writeBuf, BUF_SIZE, 0) == -1)
{
perror("send");
return -3;
}
/*if(write(sockfd, writeBuf, strlen(writeBuf))==-1)
{
perror("write");
return -3;
}*/
int a = strncasecmp(writeBuf, QUIT_FLAG, strlen(QUIT_FLAG));
if(!a)
{
printf("exited\n");
break;
}
bzero(writeBuf, BUF_SIZE);
}
close(sockfd);
return 0;
}
4.1.3 终端结果
一个服务器只能等处理完这一个之后才能处理下一个。
4.2 多进程模型
4.2.1 server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.152"
void myfunc(int signum)
{
if(signum == SIGCHLD)
{
printf("recovery complete\n");
waitpid(-1, NULL, WNOHANG);
}
}
int main()
{
int sockfd;
//1.socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd==-1)
{
perror("socket");
return -1;
}
signal(SIGCHLD, myfunc);
//2.bind
//2.1 handling protocol
struct sockaddr_in sin,cin;
bzero(&sin, sizeof(sin));
bzero(&cin, sizeof(cin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
if(bind(sockfd,(struct sockaddr*)&sin, sizeof(sin))==-1)
{
perror("bind");
return -2;
}
//3.listen
if(listen(sockfd, 5)==-1)
{
perror("listen");
return -3;
}
sleep(60);
int newfd = 0;
pid_t pid;
socklen_t addrlen = sizeof(cin);
while(1)
{
//4.accept
printf("connecting......\n");
newfd = accept(sockfd, (struct sockaddr*)&cin, &addrlen);
printf("newfd:%d\n", newfd);
if(newfd==-1)
{
perror("accept");
return -4;
}
pid = fork();
if(pid<0)
{
perror("fork");
return -5;
}
if(pid==0)
{
printf("child newfd:%d\n", newfd);
printf("succeed to connect client, cin.sin_port:%d\n", ntohs(cin.sin_port));
char readBuf[BUF_SIZE] = {0};
while(1)
{
//5.handling data;
recv(newfd, readBuf, BUF_SIZE, 0);
printf("the server accepts the content from the client is:\n");
printf("%s\n",readBuf);
if(!strncasecmp(readBuf, QUIT_FLAG, strlen(QUIT_FLAG)))
{
printf("the exited\n");
close(newfd);
return 0;
}
bzero(readBuf, BUF_SIZE);
}
}
else if(pid>0)
{
printf("child pid: %d, father pid: %d\n", pid, getpid());
}
}
close(sockfd);
return 0;
}
4.2.2 client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.151"
int main()
{
//1.socket
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("socket");
return -1;
}
//初始化配置,要连接的IP和端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
if(inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr.s_addr)<=0)
{
perror("inet_pton");
exit(1);
}
//2.connect
if(connect(sockfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
{
perror("connect");
return -2;
}
char writeBuf[BUF_SIZE] = {0};
while(1)
{
printf("====input===\n");
fgets(writeBuf, BUF_SIZE-1, stdin);
if(send(sockfd, writeBuf, BUF_SIZE, 0) == -1) //send函数跟write一个效果
{
perror("send");
return -3;
}
/*if(write(sockfd, writeBuf, strlen(writeBuf))==-1)
{
perror("write");
return -3;
}*/
int a = strncasecmp(writeBuf, QUIT_FLAG, strlen(QUIT_FLAG));
if(!a)
{
printf("exited\n");
break;
}
bzero(writeBuf, BUF_SIZE);
}
close(sockfd);
return 0;
}
4.2.3 终端结果
父进程:主管accept连接,当有连接请求时,accept创建新套接字并且创建子进程。
子进程:父子专门处理accept新创建的连接。
缺点:资源消耗严重。
4.3 多线程模型
4.3.1 server.c
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.151"
void *pthread_handle(void *arg);
int main()
{
int sockfd;
//1.socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd==-1)
{
perror("socket");
return -1;
}
//2.bind
//2.1 handling protocol
struct sockaddr_in sin,cin;
bzero(&sin, sizeof(sin));
bzero(&cin, sizeof(cin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
if(bind(sockfd,(struct sockaddr*)&sin, sizeof(sin))==-1)
{
perror("bind");
return -2;
}
//3.listen
if(listen(sockfd, 5)==-1)
{
perror("listen");
return -3;
}
int newfd = 0;
pthread_t pthid;
socklen_t addrlen = sizeof(cin);
while(1)
{
//4.accept
printf("connecting......\n");
newfd = accept(sockfd, (struct sockaddr*)&cin, &addrlen);
printf("newfd:%d\n", newfd);
if(newfd==-1)
{
perror("accept");
return -4;
}
pthread_create(&pthid, NULL, pthread_handle, (void *)&newfd); //创建进程
pthread_detach(pthid); //设置线程的分离属性,子线程不归主线程管,自己运行结束自己回收资源
}
close(sockfd);
return 0;
}
void *pthread_handle(void *arg) //线程处理函数
{
char readBuf[BUF_SIZE] = {0};
int ret = 0;
int fd = *(int*)arg;
while(1)
{
ret = read(fd, readBuf, BUF_SIZE);
if(ret<0)
{
perror("read");
break;
}
else if(!ret) continue;
printf("the content of readBuf is %s\n", readBuf);
if(!strncasecmp(readBuf, QUIT_FLAG, strlen(QUIT_FLAG)))
{
printf("the thread exited\n");
break;
}
}
}
4.3.2 client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.151"
int main()
{
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
if(inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr.s_addr)<=0)
{
perror("inet_pton");
exit(1);
}
if(connect(sockfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
{
perror("connect");
return -2;
}
char writeBuf[BUF_SIZE] = {0};
while(1)
{
printf("====input===\n");
fgets(writeBuf, BUF_SIZE-1, stdin);
if(send(sockfd, writeBuf, BUF_SIZE, 0) == -1)
{
perror("send");
return -3;
}
/*if(write(sockfd, writeBuf, strlen(writeBuf))==-1)
{
perror("write");
return -3;
}*/
int a = strncasecmp(writeBuf, QUIT_FLAG, strlen(QUIT_FLAG));
if(!a)
{
printf("exited\n");
break;
}
bzero(writeBuf, BUF_SIZE);
}
close(sockfd);
return 0;
}
4.3.3 终端结果
主线程:负责accept监听创建新的套接字。
子线程:负责数据处理。
缺点:线程资源共享。
总结
文章主要是实操,这要求你具备一些基础知识,文章如有问题请指出。