概念: 三次握手、 四次挥手
socket : tcp udp 、 广播、 组播、
并发:
多线程实现
多进程实现
第三方库来解决高并发:
select
poll : 黑菜鸟驿站一样, 首先放到驿站,然后一次收
epoll: 上面的客户端连接多了效率会地下, 这个可以解决
libevent开源库: 底层封装了 epoll
7天: Json xml 格式解析
8,9,10: 写一个小的web服务器, 模拟器http协议
1. 基础概念
1.模型
OSI七层模型: 物理层、数据层、网络层、传输层、会话层、表示层、应用层
TCP/IP 4层模型:应用程、传输层(port)、网络程(ip)、链路层
OSI:
应用层: 打入HTTP|FTP头
表示层: 编码、加密
会话层: 建立会话,TCP三次握手
传输层: TCP/UDP/端口(FTP 21、20,HTTP 80 Telent 23), 源端口|目的端口 TCP|UDP头
网络层: IP路由 源IP|目的IP IP头
数据链接层:Mac地址
物理层: 网线传输
由于osi太复杂,当时没有实现,美国国防部实现了tcp/ip,后面都用tcp/ip:
TCP/IP:
应用层: 协议HTTP、FTP、Telnet、ssh
传输层:选择TCP还是UDP服务
网络层:寻路,路由表(学习)
物理接口层: 链路传输
pop3: 邮局协议3,,邮件向客户端发送邮件协议
smtp: 客户端向邮件服务器发邮件
ICMP: ping 命令的实现
ARP: 把Ip映射成mac地址
RARP: 把mac地址抓转为Ip
NAT模式
TCP/IP数据打包:
以太网头(wifi头)|下一跳Mac地址|IP头|TCP头|HTTP协议头(应用层)|数据
以太网数据帧
在路由器:会解压以太网头、IP头,重新打包IP头,令牌环网
2. udp 协议:
16位:源端口 16位:目的端口
3. tcp 协议:
16:源端口号
16位:目的端口号
32位序列号 【发送sys时候携带的序号】
32位确定号 【 会ack的时候携带序号】
6个标志位 【ack\syn\fin\urg\pst\pyn]
16位窗口大小【 滑动窗口大小 】
4. Ip协议
网络套接字:
一个文件描述符指向一个套接字(套接字内部借助内核的2个缓冲区实现)
网络字节序:
计算机本地是 :小端存储
网络:大端存储
hton1 -> 本地-》网络IP string(1921.68.1.11->string->aoti->int->htno1-> 网络字节序
htons -> 本地-》网络(port)
ntoh1 -> 网络-》本地(IP)
ntohs -> 网络-》本地(Port)
本地IP字节序转化为网络
int inet_pton(int af, const char *src, void *dst);
af: AF_INET(ipv4)、 AF_INET6(ipv6)
src: 传入,IP地址
dst: 传出 转化后的网络字节序的IP
成功:1
异常:0, src不是一个有效的ip地址
-1 : 失败
网络转化本地IP字节序
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
struct sockaddr: 旧的已废弃
struct sockaddr_in
三次握手:
clinet 发送 sys包(建立连接请求) 带上携带序号 500 、 数据字节数、滑动窗口大小
server接收,同意建立连接回ack(501)确定序列号、数据字节数、滑动窗口大小,同时发送sys(700)包,我也要和你建立连接
cient 回ack(701)
四次挥手:
【client主动关闭】client 发送 FIN(501【携带序号】), server【被动关闭】接收 回ACK应答
那么此时client的 写缓冲区关闭,
client 此时可以收数据,不能写数据【这种状态叫做半关闭】
Server发送FIN701到client ACK(502) 502表示502之前的信息的都收到了
client 回ack
cinet 端读数据缓存区关闭,不能读数据
4次挥手 断开连接
socket中有2个缓冲区,一个用于读、一个用于写
accept、connect 执行成功,标准着三次握手完成
滑动窗口:【保证数据不丢失】
服务器返回客户端滑动窗口大小,客户端可以一直发,服务器即使没有回,服务在忙
客户端知道服务器滑动窗口大小,
如果客户一直发,服务器满了,后面的覆盖、或者丢失服务器不可行
客户端知道服务器滑动窗口大小服务器满了,客户端就不会发了
2.TCP状态分析
主动发起连接 --- 被动接收连接
主动关闭连接 ---被动关闭连接
自己理解:
TCP状态:
三次握手:
C 【主动】 端口处于CLOSE状态, 发送sys 到 s端【被动】 s端从CLOSE到LISTEN状态
S端回ACK和 SYS,C 端口处于SYS_SEND状态,S 端变成SYS_REVD状态
c 回ack , s处于ESTBLISHED状态
c 端变 ESTBLISHED状态
C 端完成三次握手
C 端处于ESTBLISHED状态,数据通信状态
四次挥手:
C【主动】端处于ESTBLISHED状态,发送fin,变成FIN_WAIT_1状态,
S【被动】 端会ACK , C端处于FIN_WAIT_2(半关闭状态),S端从ESTBLISHED 状态变成 CLOSE_WAIT状态,
S 端发FIN到C, C处于FIN_WAIT_2
C 回ACK,
四次挥手完成
S 此时 处于 TIME_WAIT,要过2ML时长,(40s) 进入CLOSE状态
主动关闭请求一端,在关闭以后经历2ML 时长以后 CLOSE
TCP状态时序图:
1. 主动发起连接请求端: CLOSE -- 发送SYN -- SEND_SYN -- 接收 ACK、SYN -- SEND_SYN -- 发送 ACK -- ESTABLISHED(数据通信态)
2. 主动关闭连接请求端: ESTABLISHED(数据通信态) -- 发送 FIN -- FIN_WAIT_1 -- 接收ACK -- FIN_WAIT_2(半关闭)
-- 接收对端发送 FIN -- FIN_WAIT_2(半关闭)-- 回发ACK -- TIME_WAIT(只有主动关闭连接方,会经历该状态)
-- 等 2MSL时长 -- CLOSE
3. 被动接收连接请求端: CLOSE -- LISTEN -- 接收 SYN -- LISTEN -- 发送 ACK、SYN -- SYN_RCVD -- 接收ACK -- ESTABLISHED(数据通信态)
4. 被动关闭连接请求端: ESTABLISHED(数据通信态) -- 接收 FIN -- ESTABLISHED(数据通信态) -- 发送ACK
-- CLOSE_WAIT (说明对端【主动关闭连接端】处于半关闭状态) -- 发送FIN -- LAST_ACK -- 接收ACK -- CLOSE
重点记忆: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)
netstat -apn | grep 端口号
11. 套接 字模型: write 以后写入缓冲区,缓冲区会自动发送出去, 收到以后
fd套接字, 对应内核的缓冲区,可以读数据、写数据,
写入的时候首先写入缓冲区, 缓冲区会自动写出去,内部是一个环形队列,读完了就没有了
3. CS模型的TCP通信分析
3.1.server
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERVER_PORT 7777
void sys_err(const char* str){
perror(str);
exit(1);
}
int main(int argc,char* argv[]){
int lfd=0,cfd=0;
int ret,i;
struct sockaddr_in serv_addr,clit_addr;
socklen_t clit_addr_len;
serv_addr.sin_family=AF_INET; // IP V4
serv_addr.sin_port=htons(SERVER_PORT); // 端口,
// htons 功能,小端转化为大端功能
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
lfd= socket(AF_INET,SOCK_STREAM,0); // IP 流式套接子 0表示根据第2个参数推断tcp
if(lfd==-1){
sys_err("socket error");
}
// int opt = 1; // 设置端口复用。
// setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
bind(lfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
listen(lfd,128); // 这里表示设置三次握手的最大并发数, 一次,同时来128个
clit_addr_len =sizeof(clit_addr);
// 上面就是三次握手
// 客户端连接服务器以后 ,获取到客户端ip和port
cfd = accept(lfd,(struct sockaddr *)&clit_addr,&clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
//clit_addr 客户端数据保存在这里
// 上面文件描述符用于监听,这里用于收、发数据
if(cfd==-1){
sys_err("accpet error");
}
char client_IP[1024]={0};
char buf[BUFSIZ];
printf("clinet ip:%s port:%lu\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)));
while(1){
ret = read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
for(i=0;i<ret;i++){
buf[i]= toupper(buf[i]);
}
write(cfd,buf,ret);
}
close(lfd);
close(cfd);
return 0;
}
3.2.client
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERVER_PORT 7777
int main(int argc,char* argv[]){
//
int lfd=0;
struct sockaddr_in serv_add;
serv_add.sin_family=AF_INET; // 协议
serv_add.sin_port=htons(SERVER_PORT); //端口
inet_pton(AF_INET,"127.0.0.1",&serv_add.sin_addr); //本地Ip 转化为网络IP 字节序
// serv_add.sin_addr.s_addr=htons(INADDR_ANY); // ip
lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd==-1){
perror("socket errror");
exit(-1);
}
int ret=connect(lfd,(struct sockaddr *)&serv_add,sizeof(serv_add)); // 客户段连接服务端
if(ret !=0){
perror("connection faile");
exit(0);
}
char buf[1024]={0};
int size=105;
while(--size){
write(lfd,"hello\n",6);
ret =read(lfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
sleep(1);
}
close(lfd);
return 0;
}
模拟客户端测试: nc 127.0.0.1 7777
服务器2个socket 一个是三次握手的lfd 一个是cfd用于通信的
客户端1个socket
例子: 首先关闭服务端,在关闭客户端,服务端处于TIME_WAIT,在启动服务器,无法启动,必须要等40s,服务端占用端口
【未得到证实】
如何解决:
端口复用:
int opt = 1; // 设置端口复用。
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
关闭socket文件描述符:
close(cfd);
shutdown(int fd, int how);
how: SHUT_RD 关读端
SHUT_WR 关写端
SHUT_RDWR 关读写
shutdown在关闭多个文件描述符应用的文件时,采用全关闭方法。close,只关闭一个。
open以后打开文件描述符f1, dup2(f1,f2) , 那么 f2 ,f1 都指向 该文件,文件描述符的引用计数+1,调用close(f1),文件描述符引用计数-1 ,只关闭了f1,f2还在, 那么如果 shutdown(f1) 那么 f1,f2都关闭
4.多进程并发服务器
出现问题解决:
问题:
1. 共享
* 读时共享,写时复制
* 文件描述符
* 内存映射区 mmap
2. 父进程的角色
* 等待客户端连接,回收关闭socket的子进程3. 子进程角色
* 通信,关闭监听文件描述符 ,避免浪费资源
4. 创建进程个数限制
* 受硬件限制
* 文件描述符默认也有上限 10245.子进程资源回收
使用信号sigaction --信号
4.1.server
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
/***
* 如何实现该功能: 父进程用于监听,每次连接启动一个子进程用于通信
* 使用信号捕捉:子进程状态变化死了,使用信号监听回收子进程中父进程中
*/
#define SERVER_PORT 7777
void sys_err(const char* str){
perror(str);
exit(1);
}
void sig_catch4(int signo) {
int status;
pid_t wpid;
// 没有子进程了回收失败
while((wpid=waitpid(-1,&status,WNOHANG)> 0)){
if(WIFEXITED(status)){
printf("WIFEXITED----%d\n",WEXITSTATUS(status));
}
}
printf("---sig_catch3---%d--", signo);
}
int main(int argc, char *argv[]) {
int lfd = 0, cfd = 0;
int ret, i;
pid_t pid;
struct sockaddr_in serv_addr, clit_addr;
socklen_t clit_addr_len;
char client_IP[1024]={0};
// memset(&serv_addr,0,sizeof(serv_addr));
bzero(&serv_addr, sizeof(serv_addr)); //和memset功能一致的
serv_addr.sin_family = AF_INET; // IP V4
serv_addr.sin_port = htons(SERVER_PORT); // 端口
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
lfd = socket(AF_INET, SOCK_STREAM, 0); // IP 流式套接子 0表示根据第2个参数推断tcp
if (lfd == -1) {
sys_err("socket error");
}
bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
listen(lfd, 128); // 这里表示设置三次握手的最大并发数
clit_addr_len = sizeof(clit_addr);
// 上面就是三次握手
while (1) {
memset(client_IP,0,sizeof(client_IP));
cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
printf("client ip:%s port:%ld\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)));
//clit_addr 客户端数据保存在这里
if (cfd == -1) {
sys_err("accpet error");
}
pid = fork();
if (pid < 0) {
sys_err("fork error");
} else if (pid == 0) {
close(lfd);
break;
} else {
struct sigaction act, oldact;
act.sa_handler = sig_catch4; //设置捕捉函数
sigemptyset(&(act.sa_mask)); // 设置mask
act.sa_flags = 0; //通常用0
int ret = sigaction(SIGCHLD, &act, &oldact); // 注册信号捕捉函数
if (ret == -1) {
perror("sigaction error");
exit(-1);
}
close(cfd);
continue;
}
}
char buf[BUFSIZ];
if (pid == 0) {
while(1){
int ret = read(cfd, buf, sizeof(buf));
if(ret==0){ //客户端关闭,已经读到套接子的末尾
close(cfd);
exit(1);
}
for (i = 0; i < ret; i++) {
buf[i] = toupper(buf[i]);
}
write(STDOUT_FILENO, buf, ret);
write(cfd, buf, ret);
}
}
return 0;
}
最终优化版本server.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
/***
* 如何实现该功能: 父进程用于监听,每次连接启动一个子进程用于通信
* 使用信号捕捉:子进程状态变化死了,使用信号监听回收子进程中父进程中
*/
#define SERVER_PORT 7777
void sys_err(const char* str){
perror(str);
exit(1);
}
void sig_catch4(int signo) {
int status;
pid_t wpid;
// 没有子进程了回收失败
while((wpid=waitpid(-1,&status,WNOHANG)> 0)){
if(WIFEXITED(status)){
printf("child died --- WIFEXITED----%d\n",WEXITSTATUS(status));
}
}
// printf("---sig_catch3---%d--", signo);
}
int main(int argc, char *argv[]) {
int lfd = 0, cfd = 0;
int ret, i;
pid_t pid;
struct sockaddr_in serv_addr, clit_addr;
socklen_t clit_addr_len;
char client_IP[1024]={0};
// memset(&serv_addr,0,sizeof(serv_addr));
bzero(&serv_addr, sizeof(serv_addr)); //和memset功能一致的
serv_addr.sin_family = AF_INET; // IP V4
serv_addr.sin_port = htons(SERVER_PORT); // 端口
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
lfd = socket(AF_INET, SOCK_STREAM, 0); // IP 流式套接子 0表示根据第2个参数推断tcp
if (lfd == -1) {
sys_err("socket error");
}
bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
listen(lfd, 128); // 这里表示设置三次握手的最大并发数
clit_addr_len = sizeof(clit_addr);
// 上面就是三次握手
// 回收子进程pcb
struct sigaction act, oldact;
act.sa_handler = sig_catch4; //设置捕捉函数
sigemptyset(&(act.sa_mask)); // 设置mask
act.sa_flags = 0; //通常用0
int ret_s = sigaction(SIGCHLD, &act, &oldact); // 注册信号捕捉函数
if (ret_s == -1) {
perror("sigaction error");
exit(-1);
}
while (1) {
memset(client_IP,0,sizeof(client_IP));
cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
// 问题? 父进程阻塞在这里, 子进程死了,父进程去处理子进程信号,回收子进程, 回收以后,accpet 不在阻塞了,返回-1
while(cfd == -1 && errno == EINTR){
cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len);
}
printf("client ip:%s port:%d\n",
inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),
ntohs(clit_addr.sin_port)
);
//clit_addr 客户端数据保存在这里
if (cfd == -1) {
sys_err("accpet error");
}
pid = fork();
if (pid < 0) {
sys_err("fork error");
} else if (pid == 0) {
printf("i am child \n");
close(lfd);
break;
} else {
printf("i am father\n");
close(cfd);
continue;
}
}
char buf[BUFSIZ];
if (pid == 0) {
while(1){
int ret = read(cfd, buf, sizeof(buf));
if(ret==0){ //客户端关闭,已经读到套接子的末尾
close(cfd);
exit(1);
}
for (i = 0; i < ret; i++) {
buf[i] = toupper(buf[i]);
}
write(STDOUT_FILENO, buf, ret);
write(cfd, buf, ret);
}
}
return 0;
}
4.2.client
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERVER_PORT 7777
int main(int argc,char* argv[]){
//
int lfd=0;
struct sockaddr_in serv_add;
serv_add.sin_family=AF_INET; // 协议
serv_add.sin_port=htons(SERVER_PORT); //端口
inet_pton(AF_INET,"127.0.0.1",&serv_add.sin_addr); //本地Ip 转化为网络IP 字节序
// serv_add.sin_addr.s_addr=htons(INADDR_ANY); // ip
lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd==-1){
perror("socket errror");
exit(-1);
}
int ret=connect(lfd,(struct sockaddr *)&serv_add,sizeof(serv_add)); // 客户段连接服务端
if(ret !=0){
perror("connection faile");
exit(0);
}
char buf[1024]={0};
int size=410;
while(--size){
write(lfd,"hello\n",6);
ret =read(lfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
sleep(1);
}
close(lfd);
return 0;
}
5.多线程服务器代码
5.1. server
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define SERVER_PORT 7777
void sys_err(const char* str){
perror(str);
exit(1);
}
struct s_info{
struct sockaddr_in cliaddr;
int connfd;
};
void* do_work(void* arg){
struct s_info *ts= (struct s_info*)arg;
char buf[BUFSIZ];
char client_IP[1024]={0};
while(1){
// * >0 实际督导字节数
// * 0 读到末尾(对端已经关闭)
// * -1: 读取失败 应该进一步判断errno的值
// errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式读
// errno = EINTR 慢速系统调用被 中断 , 解决方法重新 重新运行读取 goto
// errno =" 其他情况" 异常
int n = read(ts->connfd, buf, sizeof(buf));
if(n==0){ //客户端关闭,已经读到套接子的末尾
printf("the client %d closed...\n",ts->connfd);
break;
}
printf("receiver from client ip:%s port:%ld\n"
,inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,client_IP,sizeof(client_IP)),
ntohs((*ts).cliaddr.sin_port));
for (int i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
write(STDOUT_FILENO, buf, n);
write(ts->connfd, buf, n);
}
close(ts->connfd);
return 0;
}
int main(int argc, char *argv[]) {
int lfd = 0, cfd = 0;
int ret, i;
pid_t pid;
struct sockaddr_in serv_addr, clit_addr;
socklen_t clit_addr_len;
char client_IP[1024]={0};
struct s_info ts[256];
pthread_t tid;
// memset(&serv_addr,0,sizeof(serv_addr));
bzero(&serv_addr, sizeof(serv_addr)); //和memset功能一致的
serv_addr.sin_family = AF_INET; // IP V4
serv_addr.sin_port = htons(SERVER_PORT); // 端口
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
lfd = socket(AF_INET, SOCK_STREAM, 0); // IP 流式套接子 0表示根据第2个参数推断tcp
if (lfd == -1) {
sys_err("socket error");
}
bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
listen(lfd, 128); // 这里表示设置三次握手的最大并发数
clit_addr_len = sizeof(clit_addr);
// 上面就是三次握手
while (1) {
memset(client_IP,0,sizeof(client_IP));
cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
//clit_addr 客户端数据保存在这里
if (cfd == -1) {
sys_err("accpet error");
}
ts[i].cliaddr=clit_addr;
ts[i].connfd= cfd;
pthread_create(&tid,NULL,do_work,(void*)&ts[i]);
pthread_detach(tid);
i++;
}
return 0;
}
优化版:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define SERVER_PORT 7777
void sys_err(const char* str){
perror(str);
exit(1);
}
struct s_info{
struct sockaddr_in cliaddr;
int connfd ; // 文件描述符
pthread_t id; // 子线程id
};
void* do_work(void* arg){
struct s_info *ts= (struct s_info*)arg;
char buf[BUFSIZ];
char client_IP[1024]={0};
while(1){
// * >0 实际督导字节数
// * 0 读到末尾(对端已经关闭)
// * -1: 读取失败 应该进一步判断errno的值
// errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式读
// errno = EINTR 慢速系统调用被 中断 , 解决方法重新 重新运行读取 goto
// errno =" 其他情况" 异常
int n = read(ts->connfd, buf, sizeof(buf));
if(n==0){ //客户端关闭,已经读到套接子的末尾
printf("the client %d closed...\n",ts->connfd);
break;
}else if(n == -1){
perror("error");
pthread_exit(NULL); // 退出自己
}else{
printf("receiver from client ip:%s port:%d tid:%ld fd: %d \n"
,inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,client_IP,sizeof(client_IP)),
ntohs((*ts).cliaddr.sin_port),
ts->id,
ts->connfd
);
for (int i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
write(STDOUT_FILENO, buf, n);
write(ts->connfd, buf, n);
}
}
close(ts->connfd);
return 0;
}
int main(int argc, char *argv[]) {
int lfd = 0, cfd = 0;
int ret, i;
pid_t pid;
struct sockaddr_in serv_addr, clit_addr;
socklen_t clit_addr_len;
char client_IP[1024]={0};
struct s_info ts[256]; //为什么要定义一个数组
pthread_t tid;
// memset(&serv_addr,0,sizeof(serv_addr));
bzero(&serv_addr, sizeof(serv_addr)); //和memset功能一致的
serv_addr.sin_family = AF_INET; // IP V4
serv_addr.sin_port = htons(SERVER_PORT); // 端口
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
lfd = socket(AF_INET, SOCK_STREAM, 0); // IP 流式套接子 0表示根据第2个参数推断tcp
if (lfd == -1) {
sys_err("socket error");
}
bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
listen(lfd, 128); // 这里表示设置三次握手的最大并发数
clit_addr_len = sizeof(clit_addr);
// 上面就是三次握手
while (1) {
memset(client_IP,0,sizeof(client_IP));
cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
//clit_addr 客户端数据保存在这里
if (cfd == -1) {
sys_err("accpet error");
}
ts[i].cliaddr=clit_addr;
ts[i].connfd= cfd;
// pthread_create(&tid,NULL,do_work,(void*)&ts[i]);
pthread_create(&ts[i].id,NULL,do_work,(void*)&ts[i]);
// ts[i].id = tid;
// pthread_detach(tid);
pthread_detach(ts[i].id);
i++;
//数组满了 , 不收了
if(i==256){
break;
}
}
close(lfd);
// 数组满了,退出主线程, 子线程继续跑
pthread_exit(NULL);
return 0;
}
上面的只能开256个线程, 如果回收了保证复用
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define SERVER_PORT 7777
void sys_err(const char* str){
perror(str);
exit(1);
}
struct s_info{
struct sockaddr_in cliaddr;
int connfd ; // 文件描述符
pthread_t id; // 子线程id
};
void* do_work(void* arg){
struct s_info *ts= (struct s_info*)arg;
char buf[BUFSIZ];
char client_IP[1024]={0};
while(1){
// * >0 实际督导字节数
// * 0 读到末尾(对端已经关闭)
// * -1: 读取失败 应该进一步判断errno的值
// errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式读
// errno = EINTR 慢速系统调用被 中断 , 解决方法重新 重新运行读取 goto
// errno =" 其他情况" 异常
int n = read(ts->connfd, buf, sizeof(buf));
if(n==0){ //客户端关闭,已经读到套接子的末尾
printf("the client %d closed...\n",ts->connfd);
break;
}else if(n == -1){
perror("error");
pthread_exit(NULL); // 退出自己
}else{
printf("receiver from client ip:%s port:%d tid:%ld fd: %d\n"
,inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,client_IP,sizeof(client_IP)),
ntohs((*ts).cliaddr.sin_port),
ts->id,
ts->connfd);
for (int i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
write(STDOUT_FILENO, buf, n);
write(ts->connfd, buf, n);
}
}
close(ts->connfd);
return 0;
}
int main(int argc, char *argv[]) {
int lfd = 0, cfd = 0;
int ret, i;
pid_t pid;
struct sockaddr_in serv_addr, clit_addr;
socklen_t clit_addr_len;
char client_IP[1024]={0};
struct s_info ts[256]; //为什么要定义一个数组
pthread_t tid;
// memset(&serv_addr,0,sizeof(serv_addr));
bzero(&serv_addr, sizeof(serv_addr)); //和memset功能一致的
serv_addr.sin_family = AF_INET; // IP V4
serv_addr.sin_port = htons(SERVER_PORT); // 端口
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
lfd = socket(AF_INET, SOCK_STREAM, 0); // IP 流式套接子 0表示根据第2个参数推断tcp
if (lfd == -1) {
sys_err("socket error");
}
bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
listen(lfd, 128); // 这里表示设置三次握手的最大并发数
clit_addr_len = sizeof(clit_addr);
// 上面就是三次握手
// if have thread is exit, use
for(i=0; i< 256 ;i++){
ts[i].connfd = -1;
}
while (1) {
for(i=0; i< 256 ;i++){
if(ts[i].connfd == -1){
break;
}
}
//数组满了 , 不收了
if(i==256){
break;
}
memset(client_IP,0,sizeof(client_IP));
ts[i].connfd = accept(lfd, (struct sockaddr*) &ts[i].cliaddr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
printf("liangshang...\n");
//clit_addr 客户端数据保存在这里
if (ts[i].connfd == -1) {
sys_err("accpet error");
}
// ts[i].cliaddr=clit_addr;
// pthread_create(&tid,NULL,do_work,(void*)&ts[i]);
pthread_create(&ts[i].id,NULL,do_work,(void*)&ts[i]);
// ts[i].id = tid;
// pthread_detach(tid);
pthread_detach(ts[i].id);
// i++;
}
close(lfd);
// 数组满了,退出主线程, 子线程继续跑
printf("main exit\n");
pthread_exit(NULL);
return 0;
}