一.tcp(传输控制协议)协议简介
协议: 双方约定好的数据包格式,类似于快递公司让我们要在固定位置填收货人地址和电话号码。
特点:
1>面向连接
1)三次握手
2)四次挥手
2>保证数据包的可靠性
数据无丢失,数据包无错误, 数据包无失序,数据无重复达到
TCP协议使用的产品:
发送邮件,数据下载,QQ登录 …
3>TCP创建流程图
注意: 1. 监听套接字(套接字被设置成监听模式,专门接受客户端连接请求)
2. 每一步流程的作用
4>TCP数据传输原理
注意: 1. 客户端的socket和服务端新建立的socket之间建立连接,建立连接后,好像在服务端和客户端之间建立了两个管道, 分别负责从客户端传输数据到服务端和从服务端传输数据到客户端。
2. 服务端和每个客户端都建立了独立连接。
3. 服务端套接字和客户端套接字都有发送缓冲和接收缓冲。正是因为有缓冲的存在,才会有read write对缓冲区的读写。
二.TCP通信模型设计
服务器端:(被动接受请求)
socket //电话机
|
bind(ip+port) //绑定电话号码 。绑定服务器自己的ip和port,等待客户端连接。
|
listen //监听有人打电话进来
|
accept //接听电话
|
recv/send //通话过程
|
close //挂机
客户端:(主动发起连接)
socket //电话机
|
bind(ip+port) //绑定电话号码
|
connect //拨打电话
|
recv/send //通话过程
|
close //挂机
三.TCP常用函数的实现。
1>创建流式套接字
int socket(int domain, int type, int protocol);
功能: 创建socket ,返回对应的文件描述符
参数:
@domain 域(通信的范围)
@type SOCK_STREAM 流式套接字: 有序,可靠,面向连接 字节流
SOCK_DGRAM 报文套接字:无连接的,不可靠的
SOCK_RAW 原始套接字: 可以访问一些低层的网络协议
@protocol 0表示默认的方式
SOCK_STREAM TCP
SOCK_DGRAM UDP
返回值:
成功 文件描述符
失败 -1 ,并设置errno
2>把服务器的ip和port和sockfd绑定
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:
绑定一个地址(ip+port)到一个socket 文件描述符上
参数:
@sockfd socket 函数获得的文件描述符
@addr 地址信息结构体
//通用结构体
struct sockaddr {
sa_family_t sa_family; //地址族
char sa_data[14]; //地址信息
}
//TCP/IP协议的地址结构
struct sockaddr_in {
sa_family_t sin_family; //协议簇
in_port_t sin_port; //端口
struct in_addr sin_addr; //ip地址
};
@addrlen 表示 addr 参数对应类型的地址信息结构体的大小
返回值:
成功 0
失败 -1&errno
操作:
1>.定义地址结构体变量,清零
struct sockaddr_in ser_addr;
memset(&ser_addr,0,sizeof(ser_addr));
2>.填充地址信息
ser_addr.sin_family = AF_INET;//地址协议族
ser_addr.sin_port = htons(8888);//端口号
ser_addr.sin_addr = inet_addr("192.168.2.7");
3>. 绑定
if(bind(sockfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr)) < 0)
{
perror("bind fail");
exit(EXIT_FAILURE);
}
3>获得客户连接请求,创建连接连接套接字。负责数据通信。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能: 获取连接请求,建立连接套接字
参数:
@sockfd 监听套接字
@addr 用来获取对端的地址信息
@addrlen 值结果参数(注意:必须自己初始化一个值sizeof(addr))
返回值:
成功 新连接的套接字文件描述符
失败 -1 ,并设置错误码。
特点:
1.请求队列中没有请求,阻塞调用者
2.每次提取一路请求,就会创建一个新的套接字,我们称为连接套接字,用来和客户端收发数据
int listen(int sockfd ,int backlog);
功能:把同一个时刻来的套接字存放如监听队列中。<同一个时刻>
参数:
@sockfd 监听的套接字
@backlog 连接请求队列的最大长度。一般设置为30 以下。
注意:有的时候backlog很小,但是我们也可以了解很多台机器。服务器机器处理速度很快队列来不及填满就处理完了,而且在同一个时刻到来的连接还是很少的
4>客户端向服务器请求连接的函数
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:向服务端发起连接。
参数:
@sockfd 客户端的socket文件描述符
@addr 代表服务器端的地址信息
@addrlen addr 对应的地址结构体类型的大小
返回值:
成功返回0,失败返回-1
5>发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:给指定的sockfd发送数据
参数:
@scokfd 套接字
@ buf 发送数据的缓冲区
@len 要发送数据的长度
@flags 设置网络标志 ,常常设置为0
返回值:
成功,返回值 > 0,发送成功的字节数
返回值==0,发送超时
返回值<0, 发送失败,并设置错误码
//在阻塞模式下,下面的公式等价
注:send(sockfd, buf, len, 0); <===> write(sockfd, buf, len);
6>接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收指定的sockfd的数据即连接套接字返回的文件描述符
参数:
@scokfd 套接字
@ buf 接受数据的缓冲区
@len 要接受数据的长度
@flags 设置网络标志 ,常常设置为0
返回值:
成功,返回值 > 0,成功接收的字节数
返回值==0,对方关闭了连接
返回值<0, 发送失败,并设置错误码
//在阻塞模式下,下面的公式等价
注:recv(sockfd, buf, len, 0); <===> read(sockfd, buf, len);
练习:
1.TCP服务端和客户端的代码看懂。然后客户端发送数据,服务端接收数据,然后服务端打印接收到的内容,在原样的发送给客户端,之后,客户端再来接收数据,之后把客户端再把数据打印到屏幕上。依次如此。
客户端 服务端
send---------->recv
recv <--------send
Service.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <string.h>
#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int listen_fd = 0;
int connect_fd = 0;
struct sockaddr_in my_addr;
struct sockaddr_in client_addr;
int len = sizeof(my_addr);
int n = 0 ;
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
//1.通过socket创建监听套接字
listen_fd = socket(AF_INET,SOCK_STREAM,0);
if(listen_fd < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
//2.填充服务器自己的ip地址和端口
memset(&my_addr,0,sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(atoi(argv[2]));
my_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.把ip和port进行绑定
if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
{
perror("Fail to bind");
exit(EXIT_FAILURE);
}
//4.监听客户端的连接
listen(listen_fd,5);
printf("Listen....\n");
//5.准备接收客户端的连接请求
connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);
if(connect_fd < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
printf("=============================================");
printf("connect_fd : %d\n",connect_fd);
printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
printf("client port : %d\n", ntohs(client_addr.sin_port));
while(1)
{
memset(buf,0,sizeof(buf));
n = recv(connect_fd,buf,sizeof(buf),0);
if(n < 0)
{
perror("Fail to recv!\n");
exit(EXIT_FAILURE);
}else if(n == 0){
printf("clinet is not connect\n");
exit(EXIT_FAILURE);
}
if(strncmp(buf,"quit",4) == 0)
break;
printf("Recv %d bytes : %s\n",n,buf);
}
close(listen_fd);
close(connect_fd);
exit(EXIT_SUCCESS);
}
Client.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <string.h>
#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int sockfd = 0;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int len = sizeof(server_addr);
int n = 0 ;
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
//1.通过socket创建监听套接字
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
//2.填充服务器自己的ip地址和端口
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.准备向服务端的请求连接
if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
while(1)
{
memset(buf,0,sizeof(buf));
putchar('>');
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';
n = send(sockfd,buf,strlen(buf),0);
if(n < 0)
{
perror("Fail to recv!\n");
exit(EXIT_FAILURE);
}else if(n == 0){
printf("clinet is not connect\n");
exit(EXIT_FAILURE);
}
if(strncmp(buf,"quit",4) == 0)
break;
}
close(sockfd);
exit(EXIT_SUCCESS);
}
上面的主要是只能实现一对一的通信。服务器开启之后,只能有一路连接。
四.常见服务器模型
1>TCP循环服务器
TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接.
算法如下:
socket(...); //创建一个TCP套接字
bind(...); //绑定公认的端口号
listen(...); //监听客户端连接
while(1) //开始循环接收客户端连接
{
accept(...); //接收当前客户端的连接
while(1)
{ //处理当前客户端的请求
read(...);
process(...);
write(...);
}
close(...); //关闭当前客户端的连接,准备接收下一个客户端连接
}
注意:
TCP循环服务器一次只能处理一个客户端的请求.只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求.这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.因此,TCP服务器一般很少用循环服务器模型的.
service.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <string.h>
#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int listen_fd = 0;
int connect_fd = 0;
struct sockaddr_in my_addr;
struct sockaddr_in client_addr;
int len = sizeof(my_addr);
int n = 0 ;
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
//1.通过socket创建监听套接字
//创建socket并返回对应的文件描述符listen_fd
listen_fd = socket(AF_INET,SOCK_STREAM,0);
if(listen_fd < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
//2.填充服务器自己的ip地址和端口
memset(&my_addr,0,sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(atoi(argv[2]));
my_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.把ip和port进行绑定
if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
{
perror("Fail to bind");
exit(EXIT_FAILURE);
}
//4.监听客户端的连接
listen(listen_fd,5);
printf("Listen....\n");
while(1)
{
//5.接收客户端的连接请求,创建连接套接字,返回新建立的
//套接字文件描述符connect_fd
connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);
if(connect_fd < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
printf("=============================================\n");
printf("connect_fd : %d\n",connect_fd);
printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
printf("client port : %d\n", ntohs(client_addr.sin_port));
while(1)
{
memset(buf,0,sizeof(buf));
//6.接受数据
n = recv(connect_fd,buf,sizeof(buf),0);
//返回值>0,成功接收的字节数
//返回值==0,对方关闭了连接
//返回值<0,发送失败,并设置错误码
if(n < 0)
{
perror("Fail to recv!\n");
exit(EXIT_FAILURE);
}else if(n == 0){
printf("clinet is not connect\n");
exit(EXIT_FAILURE);
}
if(strncmp(buf,"quit",4) == 0)
break;
printf("Recv %d bytes : %s\n",n,buf);
}
}
close(listen_fd);
close(connect_fd);
exit(EXIT_SUCCESS);
}
client.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <string.h>
#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int sockfd = 0;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int len = sizeof(server_addr);
int n = 0 ;
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
//1.通过socket创建监听套接字
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
//2.填充服务器的ip地址和端口
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.准备向服务端的请求连接
if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
while(1)
{
memset(buf,0,sizeof(buf));
putchar('>');
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';
n = send(sockfd,buf,strlen(buf),0);
if(n < 0)
{
perror("Fail to recv!\n");
exit(EXIT_FAILURE);
}else if(n == 0){
printf("clinet is not connect\n");
exit(EXIT_FAILURE);
}
if(strncmp(buf,"quit",4) == 0)
break;
}
close(sockfd);
exit(EXIT_SUCCESS);
}
通过不同的客户机给服务器发送数据,但每次服务器只会处理一个客户端。
并发服务器:
为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型. 并发服务器的思想是
每一个客户机的请求并不由服务器直接处理,而是服务器创建一个"子进程"[线程]来处理. 算法如下:
1. 多进程TCP并发服务器
socket(...);
bind(...);
listen(...);
while(1)
{
connect_fd = accept(...);
pid = fork();
if(pid == 0) {
close(listen_fd) ; //创建子进程,套接字复制一份,关闭不需要使用。
while(1)
{
read(...);
process(...);
write(...);
}
}
close(connect_fd); //父进程关闭连接套接字,只需要监听即可。
close(...);
}
service.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#define N 1024
void do_client(int sockfd)
{
char buf[N] = {0};
int n = 0;
while(1)
{
memset(buf,0,sizeof(buf));
n = recv(sockfd,buf,sizeof(buf),0);
if(n < 0)
{
perror("Fail to recv!\n");
exit(EXIT_FAILURE);
}else if(n == 0){
printf("clinet is not connect\n");
exit(EXIT_FAILURE);
}
if(strncmp(buf,"quit",4) == 0)
break;
printf("Recv %d bytes : %s\n",n,buf);
}
exit(EXIT_SUCCESS);//子进程退出后会造成僵尸态子进程
}
void handler_process(int signum)
{
waitpid(-1,NULL,WNOHANG);
printf("chlid exit is success!\n");
return;
}
//./a.out ip port
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int listen_fd = 0;
int connect_fd = 0;
struct sockaddr_in my_addr;
struct sockaddr_in client_addr;
int len = sizeof(my_addr);
int n = 0 ;
pid_t pid; //unsigned int
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
//对僵尸态子进程进行回收
//子进程结束时会给父进程发一个SIG信号,此时调用
//回收僵尸态子进程处理函数
if(signal(SIGCHLD,handler_process) == SIG_ERR){
perror("Fail to signal");
exit(EXIT_FAILURE);
}
//1.通过socket创建监听套接字
listen_fd = socket(AF_INET,SOCK_STREAM,0);
if(listen_fd < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
//2.填充服务器自己的ip地址和端口
memset(&my_addr,0,sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(atoi(argv[2]));
my_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.把ip和port进行绑定
if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
{
perror("Fail to bind");
exit(EXIT_FAILURE);
}
//4.监听客户端的连接
listen(listen_fd,5);
printf("Listen....\n");
while(1)
{
//5.准备接收客户端的连接请求
connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);
if(connect_fd < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
printf("=============================================\n");
printf("connect_fd : %d\n",connect_fd);
printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
printf("client port : %d\n", ntohs(client_addr.sin_port));
pid = fork();
if(pid < 0)
{
perror("Fail to fork");
exit(EXIT_FAILURE);
}else if(pid == 0){ //child process do something
close(listen_fd); //防止子进程浪费文件描述符
do_client(connect_fd);
}else if(pid > 0){
close(connect_fd); //父进程关闭连接套接字 ,只需要进行监听即可
}
}
exit(EXIT_SUCCESS);
}
client.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <string.h>
#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int sockfd = 0;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int len = sizeof(server_addr);
int n = 0 ;
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
//1.通过socket创建监听套接字
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
//2.填充服务器自己的ip地址和端口
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.准备向服务端的请求连接
if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
while(1)
{
memset(buf,0,sizeof(buf));
putchar('>');
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';
n = send(sockfd,buf,strlen(buf),0);
if(n < 0)
{
perror("Fail to recv!\n");
exit(EXIT_FAILURE);
}else if(n == 0){
printf("clinet is not connect\n");
exit(EXIT_FAILURE);
}
if(strncmp(buf,"quit",4) == 0)
break;
}
close(sockfd);
exit(EXIT_SUCCESS);
}
2. 多线程TCP并发服务器 。
//线程处理函数
void *do_client(void *age)
{
int connect_fd = *(int *)arg;
while(1)
{
//接收客户端数据
recv();..
}
}
socket(...);
bind(...);
listen(...);
while(1)
{
pconnect_fd = malloc(sizeof(int)); //在堆区创建空间保持文件描述符
*pconnect = accept(...);
//创建线程接收数据
ret = pthread_creat(&tind,NULL,do_client,pconnect_fd);
close(...);
exit(...);
}
pthread_detach(tid);
}
思考:这里为什么需要在堆区创建空间呢?
答案:它主要是在堆区创建空间用于保存每个客户端的连接套接字。若是局部变量的话,每来了一个用户就会把之间的值给覆盖掉。若是全局变量,也是一样,每来了一个用户就会把之间的值给覆盖掉。若是数组,也可以,但是由于不知道连接用户的个数,数组比较浪费空间。
service.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#define N 1024
void *do_client(void *arg)
{
int n = 0;
char buf[N] = {0};
int sockfd = *(int *)arg;
//下面pthread_create传递过来的参数
//即连接套接字的地址,sockfd就是连接套接字了
while(1)
{
memset(buf,0,sizeof(buf));
n = recv(sockfd,buf,sizeof(buf),0);
if(n < 0)
{
perror("Fail to recv!\n");
exit(EXIT_FAILURE);
}else if(n == 0){
printf("clinet is not connect\n");
exit(EXIT_FAILURE);
}
if(strncmp(buf,"quit",4) == 0)
break;
printf("Recv %d bytes : %s\n",n,buf);
}
close(sockfd);
free(arg);//释放堆区空间
pthread_exit(NULL);
}
//./a.out ip port
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int listen_fd = 0;
int connect_fd = 0;
struct sockaddr_in my_addr;
struct sockaddr_in client_addr;
int len = sizeof(my_addr);
int n = 0 ;
int *pconnect_fd = NULL;
pthread_t tid;
int ret = 0;
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
//1.通过socket创建监听套接字
listen_fd = socket(AF_INET,SOCK_STREAM,0);
if(listen_fd < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
//2.填充服务器的ip地址和端口
memset(&my_addr,0,sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(atoi(argv[2]));
my_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.把ip和port进行绑定
if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
{
perror("Fail to bind");
exit(EXIT_FAILURE);
}
//4.监听客户端的连接
listen(listen_fd,5);
printf("Listen....\n");
while(1)
{
//由于每次有用户连接的时候,系统都会调用accept来返回连接套接字。
//若是同时两个2个用户请求连接,可能存在,后一个线程把前一个线程
//的数据给覆盖点,因此在堆区申请空间,每一个连接套接字拥有不同的
//地址空间.
pconnect_fd = (int *)malloc(sizeof(int));
//5.准备接收客户端的连接请求
*pconnect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);
if(*pconnect_fd < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
printf("=============================================");
printf("connect_fd : %d\n",*pconnect_fd);
printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
printf("client port : %d\n", ntohs(client_addr.sin_port));
ret = pthread_create(&tid,NULL,do_client,(void *)pconnect_fd);
//pconnect_fd是给线程函数do_client传递的参数
if(ret != 0)
{
fprintf(stderr,"Fail to pthread_create : %s\n",strerror(errno));
exit(EXIT_FAILURE);
}
//分离式线程:线程结束的时候由系统自动来释放资源
pthread_detach(tid);
}
exit(EXIT_SUCCESS);
}
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <string.h>
#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
int sockfd = 0;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int len = sizeof(server_addr);
int n = 0 ;
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
//1.通过socket创建监听套接字
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
//2.填充服务器自己的ip地址和端口
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.准备向服务端的请求连接
if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
while(1)
{
memset(buf,0,sizeof(buf));
putchar('>');
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';
n = send(sockfd,buf,strlen(buf),0);
if(n < 0)
{
perror("Fail to recv!\n");
exit(EXIT_FAILURE);
}else if(n == 0){
printf("clinet is not connect\n");
exit(EXIT_FAILURE);
}
if(strncmp(buf,"quit",4) == 0)
break;
}
close(sockfd);
exit(EXIT_SUCCESS);
}
注:TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况. 不过也同时带来了一个不小的问题.为了响应客户机的请求,服务器要创建子进程来处理. 而创建子进程/子线程是一种非常消耗资源的操作.