目录
第一部分 静态库与动态库的建立 静态库的建立和使用
建立
1 g++ -c -o libpublic.a public .cpp
其中libpublic.a表示静态库,public.cpp表示需要建立静态库的代码
使用
1 g++ -o demo01 demo01. cpp -L/home/pigcanstudy/tools -lpublic
其中-L表示静态库所在目录,-l表示静态库的库名
动态库的建立和使用
makefile 为什么要引入make这个工具
静态库与动态库在建立的时候很麻烦,如果有很多项目文件选哟建立库,就可以使用make工具,使用.sh脚本工具也能实现,即建立一个.sh的脚本,里面加入你要建立库的语句,如图所示: 可以用 sh命令来执行
makefile的使用与细节
main函数的参数 1 int main (int argc, char * argv[], char *envp[])
argc 存放了程序参数的个数,包括程序本身
argv 字符串数组,存放了每个参数的值,包括程序本身
envp 字符串数组,存放了环境变量,数组最后一个元素为空 这参数常用于判断一个人是否能进入程序(如表白程序)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 } #include <iostream> int main (int argc, char * argv[],char * envp[]) { std::cout << "一共有" << argc << "个参数\n" ; for (int i = 0 ; i < argc; i ++){ std:: cout << "第" << i << "个参数: " << argv[i] << std::endl; } setenv ("hello" , "HELLO" , 0 ); std::cout << getenv ("hello" ) << std::endl; return 0 ; }
gdb调试程序 如果需要程序可调试,编译时需要加-g选项,并且,不能使用-O的优化选项
gdb的常用命令
用gdb调试core文件
ulimit 的参数 : 要修改哪个参数 就 - 啥
gdb排查死锁的方法
1 . 先使用
1 g++ --std=c++11 -g -O0 dead_lock.cpp -o dead_lock
发生死锁后 程序会卡住,一般会有一个看门狗程序,对卡住的程序发送kill信号,这时候被杀死的信号会产生core文件
然后执行以下命令来分析core文件
1 gdb -c ./core-49237 ./dead_lock
并用bt命令查看调用栈
在gdb调试下使用 info threads ,查看当前有多少线程
使用thread 加对应编号 以及 bt 查看线程在干啥
使用f 加对应编号 来查看对应调用栈干了什么
使用 p 加你要查看的变量 查看其参数 owner 不为0就表示他被谁占有着,使用 info threads 能看线程的对应线程号
用gdb调试一个正在运行的程序
首先需要知道运行程序的进程编号是什么? 使用 ps -ef |grep demo
然后使用 gdb demo -p 进程编号
正在运行的程序被调试了会自动停下来
linux的时间操作
C++11提供了操作时间的chrono库
但这个库屏蔽了很多细节 所以我们为了更好使用它,得了解他的底层
time_t 别名
这是一个时间类型,他是一个long类型的别名,在文件中定义,表示从1970.1.1到现在的秒数
推荐使用 time_t定义它 可用这个 简化书写 typedef long time_t。
time()库函数
tm结构体(timeval)
localtime()库函数
这个函数的作用是把time_t表示的时间转变为tm结构体表示的时间
localtime()函数不是线程安全的,localtime_r()是线程安全的
函数声明
mktime()函数 与localtime()功能相反
gettimeofday()函数
用于获取从1970.1.1到现在的秒和当前秒中已逝去的微秒数,可以用于程序的计时
函数声明:
程序的睡眠
把程序挂起一段时间 sleep()函数 与usleep()函数
前者单位是秒 后者是微秒
包含在头文件中
linux 获取目录操作 几个简单的目录操作函数 获取当前工作目录
即 getcwd()函数,这个函数的作用是获取当前的工作目录,需要提前指定好容量
get_current_dir_name(void) 这个函数 功能也是获取当前的工作目录,但是它是 动态分配内存,其内部是使用malloc()函数 所以需要手动free()它
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> #include <unistd.h> int main () { char path1[256 ]; getcwd (path1, 256 ); std::cout << "path1 = " << path1 << std::endl; char * path2 = get_current_dir_name (); std::cout << "path2= " << path2 << std::endl; free (path2); }
切换工作目录
创建目录 其中有两个参数 一个是目录名 一个是访问权限 注意:权限是八进制数不要省略前置的0 也就是说 0755
删除目录
获取目录中文件的列表
包含头文件
相关的库函数
目录指针 DIR ,声明方式DIR* 指针名字
readdir(),返回结构体 dirent的地址,存放了本次读取到的内容
结构体定义如下:
重点关注d_type和d_name成员,其中文件类型有多种取值,最重要的是8和4,8表示常规文件,4表示目录
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <dirent.h> int main (int argc, char * argv[]) { if (argc != 2 ){ std::cout << "Using ./mulu1 目录名\n" ; return -1 ; } DIR* dir; if ((dir = opendir (argv[1 ])) == nullptr ) return -1 ; struct dirent *stdinfo = nullptr ; while (1 ){ if ((stdinfo = readdir (dir)) == nullptr ) break ; std::cout << "文件名: " << stdinfo->d_name << ", 文件类型= " << (int )stdinfo->d_type << std::endl; } return 0 ; }
linux 的系统错误
在C++程序中,如果调用了库函数,如果失败了会在全局变量errno 中存放调用过程产生错误代码的原因,所以我们可以用errno 变量来查找原因
头文件:
需要配合sterror()和perror()两个函数库,来获取详细信息
sterror()库函数
1 2 char * strerror (int errnum) ; int strerror_r (int errnum, char *buf, size_t buflen) ;
示例代码:
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> #include <string.h> #include <cerrno> #include <sys/stat.h> int main () { int ir = mkdir ("/home/pigcanstudy/test1/123" , 0755 ); std::cout << "ir= " << ir << std::endl; std::cout << errno << ":" << strerror (errno) << std::endl; return 0 ; }
perror()库函数
在 中,用于在控制台显示最近一次系统错误的详细信息
注意事项
并不是所有库函数调用失败都会在errno中存储,以man 手册为主(不属于系统调用的函数不会设置errno)
errno不能作为调用库函数的失败标志 因为errno 在调用成功后 不会改变值,也就是说不会主动置为0(0表示成功)
目录和文件的更多操作 access()库函数
此函数用于判断当前用户对目录或文件的存储权限是否满足
包含在头文件中 四种权限
返回值 当pathname满足mode权限返回0,不满足返回-1,errno 被设置
在实际开发中 access()函数主要用于判断目录或文件是否存在 示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <cstdio> #include <sys/stat.h> #include <unistd.h> int main (int argc, char * argv[]) {if (argc != 2 ){ std::cout << "Using ./access 文件或目录名\n" ; return -1 ; } if (access (argv[1 ],F_OK) != 0 ){ std::cout << "文件或目录" << argv[1 ] << "不存在\n" ; return -1 ; } std::cout << "文件或目录" << argv[1 ] << "存在\n" ; return 0 ;}
stat结构体
stat 结构体用于存放目录或文件的详细信息,如下:
重点关注st_mode,st_size和st_mtime成员 , st_mtime是一个整数表示的时间,需要程序员自己写代码转换格式
stat()库函数
utime()库函数
用于修改目录或文件的时间
结构体 utimbuf
rename()库函数
remove()库函数
Linux的信号 信号的基本概念
信号是软件中断,是进程之间相互传递消息的一种方法,用于通知进程发生了事件,但是,不能给进程传递任何数据
在shell中使用kill 和 killall 命令发送信号
kill 与 killall 的区别
信号的类型
重要的类型
SIGINT 2 A 键盘中断
SIGKILL 9 AEF 采用kill -9 进程编号强制杀死程序
SIGALRM 14 A 由闹钟alarm()函数发出的信号
SIGTERM 15 A 采用 kill 进程编号 或 killall 程序名来通知程序
SIGCHLD 17 B 子进程结束信号
SIGSEGV 11 CEF 无效的内存引用(数组越界,操作空指针和野指针)
其中字母表示默认缺省的类型
信号的处理
大部分默认操作是终止进程
设置信号处理函数(signal() 函数)
忽略某个信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <unistd.h> #include <signal.h> void func (int signum) { std::cout << "收到了信号: " << signum << std::endl; } void func1 (int sig) { std::cout << "收到了闹钟信号,完成了定时任务\n" ; alarm (5 ); } int main () { signal (1 ,func); signal (15 ,func); signal (SIGINT, SIG_IGN); signal (9 , func); signal (9 , SIG_IGN); alarm (5 ); signal (14 , func1); while (1 ){ std::cout << "执行了一次任务。\n" ; sleep (1 ); } return 0 ; }
SIG_DEF: 恢复参数signum 所指信号的处理方法为默认值
自定义的信号比如func
SIG_IGN: 忽略参数signum所指的信号
信号有什么用
可以在杀死进程的时候进行善后工作,比如释放内存啥的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <signal.h> #include <unistd.h> void EXIT (int sig) { std::cout << "收到了信号:" << sig << std::endl; std::cout << "正在释放资源,程序将退出...." << std::endl; std::cout << "程序退出" << std::endl; exit (0 ); } int main () { signal (2 , EXIT); signal (15 , EXIT); while (1 ){ sleep (1 ); } return 0 ; }
向程序发送0信号,可以检测程序是否存活
发送信号 可以使用kill()函数向其他进程发送信号
函数声明 int kill(pid_t pid, int sig);
sig表示是啥信号
pid 表示指定的进程号
pid的几种情况
pid>0 将信号传给进程号为pid的进程
pid=0 将信号传递给和目前进程相同进程组的所有进程常用于父进程给子进程发送信号,注意,发送信号者也会收到自己发出的信号
pid=-1 将信号广播发送给系统内所有进程,例如系统关机时,会向所有的登录窗口广播关机信息
进程终止 进程终止方式
有8种方式可以终止进程
其中五种为正常终止
main()函数用return 返回
用exit()退出
调用_exit()或_Exit()函数
最后一个线程从启动例程(线程主函数)用return返回
在最后一个线程中调用pthread_exit()返回
异常终止有三个方式
调用abort() 函数
接收到一个信号
最后一个线程对取消请求做出响应
进程终止状态
默认终止状态为0
查看进程终止状态为 echo $?
3个正常终止函数的参数就是状态
异常终止 状态为非0
资源释放的问题
return 在哪个函数中返回就会清理哪个局部对象的析构函数,在main中还会调用全局对象的析构
exit()不会调用局部对象的析构,会调用全局对象析构
exit()会执行清理工作,在退出 而_exit(),_Exit()会直接退出,不执行清理工作
进程的终止函数
atexit()函数登记终止函数最多32个,这些函数由exit()自动调用
int atexit(void(*function)(void));
exit()调用终止函数顺序与登记时相反
可以干些进程终止前的收尾工作
调用可执行程序 system()库函数 功能 调用可执行程序,把需要执行的程序和参数用一个字符串传给system()就好了 头文件可用 man system()来查看
返回值 1 2 3 4 5 6 7 8 #include <iostream> #include <unistd.h> int main () { int ret = system ("/bin/ls -l /tmp" ); std::cout << "ret=" << ret << std::endl; perror ("system" ); }
exec函数族 exec函数族提供了另一种在进程中调用程序(二进制文件或shell脚本)的方法
其中重要的已经加粗
execl()函数 1 2 3 4 5 6 7 8 9 10 #include <iostream> #include <unistd.h> #include <string.h> int main (int argc, char * argv[]) { int ret = execl ("/bin/ls" , "/bin/ls" , "-lt" , "/tmp" , 0 ); std::cout << "ret=" << ret << std::endl; perror ("execl" ); }
注意事项:
其中第二点尤为重要,新进程的进程编号与原进程相同,但是,新进程取代了原进程的代码段,数据段,和堆栈 举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <unistd.h> #include <string.h> int main (int argc, char * argv[]) { std::cout << "demo 本进程的编号是: " << getpid () << std::endl; int ret = execl ("/home/pigcanstudy/test1/demo01" ,"/home/pigcanstudy/test1/demo01" ,0 ); std::cout << "ret=" << ret << std::endl; perror ("execl" ); }
execv()函数 注意 char* 必不可少否则会报错
创建进程 Linux的0,1,2号进程
进程标识
fork()函数 用来创建一个子进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <string> #include <unistd.h> int main () { int bh = 8 ; std::string message="我是一只小猪" ; pid_t pid = fork(); pid_t pid1 = fork(); if (pid > 0 ){ std::cout << "fu" << std::endl; std::cout << pid << std::endl; } else { std::cout << "zi" << std::endl; std::cout << pid << std::endl; } if (pid1 > 0 ){ std::cout << "fu" << std::endl; std::cout << pid1 << std::endl; } else { std::cout << "zi" << std::endl; std::cout << pid1 << std::endl; } return 0 ; }
fork()的两种用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <unistd.h> int main () { if (fork() > 0 ){ while (true ){ sleep (1 ); std::cout << "父进程运行中...\n" ; } } else { sleep (10 ); std::cout << "子进程开始执行任务....\n" ; execl ("/home/pigcanstudy/test1/demo01" , "/home/pigcanstudy/test1/demo01" , 0 ); std::cout << "子进程执行任务结束,退出\n" ; } return 0 ; }
补充 shell的原理是在bash这个进程下创建子进程来执行你写的操作
共享文件 对这句话的解释就是父子进程中,如果父进程先执行完文件写操作,子进程会接着父进程的文件指针的下一个来写,也就是说不会覆盖文件的内容,而是接在后面
需要采用进程同步
vfork()
僵尸进程 僵尸进程的产生
对于第一句话的解释 代码: 结果:
将一个可执行程序放在后台运行有两种方法:
直接执行 ./demo &
在程序的代码中 加入一行if(fork() > 0) return 0;
僵尸进程地避免
子进程退出的时候,内核会给父进程发出SIGCHLD信号,如果父进程调用了signal(SIGCHLD,SIG_IGN)来通知内核,内核就会把这个子进程的数据结构释放,但是父进程得不到子进程被释放的信息
使用wait或waitpid 如图:
如果父进程很忙,可以捕获SIFCHLD信号,在信号处理函数中调用wait()/waitpid()
多进程与信号
在多进程的服务程序中,如果子进程收到退出信号,子进程自行退出,如果父进程收到退出信号,应该先向所有子进程发送退出信号,再自己退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <iostream> #include <unistd.h> #include <signal.h> void FatherEXIT (int sig) ;void ChildEXIT (int sig) ;int main () { for (int i = 0 ; i < 65 ; i ++) signal (i, SIG_IGN); signal (SIGTERM, FatherEXIT); signal (SIGINT, FatherEXIT); while (true ){ if (fork() > 0 ){ sleep (5 ); continue ; } else { signal (SIGTERM, ChildEXIT); signal (SIGINT, SIG_IGN); while (true ){ std::cout << "子进程" << getpid () << "正在运行中。\n" ; sleep (3 ); continue ; } } } return 0 ; } void FatherEXIT (int sig) { signal (SIGTERM, SIG_IGN); signal (SIGINT, SIG_IGN); std::cout << "父进程" << getpid () << "正在退出, sig=" << sig << std::endl; kill (0 ,SIGTERM); std::cout << "子进程" << getpid () << "正在释放父进程的资源以及全局资源" << std::endl; exit (0 ); } void ChildEXIT (int sig) { signal (SIGTERM, SIG_IGN); signal (SIGINT, SIG_IGN); std::cout << "子进程" << getpid () << "正在退出, sig=" << sig << std::endl; std::cout << "子进程" << getpid () << "正在释放子进程的资源" << std::endl; exit (0 ); }
共享内存
shmget()函数
shmat()函数
shmdt()函数
shmctl()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <unistd.h> #include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h> struct node { int no; char name[51 ]; }; int main (int argc, char * argv[]) { int shmid = shmget (0x5005 , sizeof (node), 0640 |IPC_CREAT); if (shmid == -1 ){ std::cout << "shgmget(0x5005) failed.\n" ; return -1 ; } std::cout << "shmid=" << shmid << std::endl; node *ptr = (node *)shmat (shmid,0 ,0 ); if (ptr == (void *)-1 ){ std::cout << "shmat() failed\n" ; return -1 ; } std::cout << "原值:no=" << ptr->no << ",name=" << ptr->name << std::endl; ptr->no = atoi (argv[1 ]); strcpy (ptr->name, argv[2 ]); std::cout << "新值:no=" << ptr->no << ",name=" << ptr->name << std::endl; shmdt (ptr); if (shmctl (shmid, IPC_RMID,0 ) == -1 ){ std::cout << "shmctl failed\n" ; } return 0 ; }
注意分配共享内存的时候不能分配堆区,所以在数据结构中的stl那些都不能分配
循环队列
原理
细节:用来动态使用https://www.bilibili.com/video/BV11Z4y157RY/?p=22&spm_id_from=pageDriver&vd_source=772b61fb408cdf9169f726ab21790987
信号量
p操作wait 将信号量的值减一,如果信号量的值为0,将阻塞等待,知道信号量大于0
V操作post 将信号量的值加1 任何时候都不会阻塞
https://www.bilibili.com/video/BV11Z4y157RY/?p=23&spm_id_from=pageDriver&vd_source=772b61fb408cdf9169f726ab21790987
pthread 线程库
第二部分 第一个网络通信程序户端 基于linux的文件操作 文件描述符的分配规则
linux底层的read系统调用和write系统调用,也能用在socket中
socket函数详解 创建socket 函数的声明
其中 domain 参数用来指定协议家族:重要参数 IP_INET
type 表示数据的传输类型
protocol协议
两种创建方式
几乎全部的网络编程函数失败时都会返回-1,并且errno被设置,单个进程中创建的socket数量受系统的openfile限制(ulimit -a)
主机字节序和网络字节序 大端序和小端序
大端序:低位字节存放在高位,高位字节存放在低位
小端序:地位字节存放在地位,高位字节存放在高位
举例:
问题:
网络字节序
为了解决主机字节序不同的问题,引入了网络字节序(是一种大端序)
其中 h 表示host(主机)
to 转换
n network 网络
s short 二字节 16位整数
l long 四字节 32位整数
IP地址和通讯端口
如何处理大小端序
数据收发的时候有自动转换,只有向sockaddr_in 结构体成员变量填充数据的时候才需要考虑字节序问题
万恶的结构体
sockaddr结构体
用来存放协议族,端口和地址信息,客户端和connect()函数和bind()函数需要这个结构体
sockaddr_in结构体 sockaddr结构体是为了统一接口函数,操作起来不方便,所以加了这个等价的结构体
也就是说 后者的结构体是为了方便操作而设计的
在程序中我们操作的是sockaddr_in结构体,在调用函数的时候我们调用的soockaddr结构体
细说此结构体内部的细节
首先由于它的大小与sockaddr相同所以可以强制转换为sockaddr
但操作ip地址成员的时候 我们该怎么办?
使用gethostbyname()函数
函数声明以及其返回结构体的内容如下:
举例:
1 2 3 4 5 6 7 8 struct hostent * h; if ((h = gethostbyname (argv[1 ])) == nullptr ){ std::cout << "gethostbyname failed.\n" ; close (sockfd); return -1 ; } memcpy (&servaddr.sin_addr, h->h_addr,h->h_length);
字符串IP与大端序IP的转换
C语言提供了一个库函数,用于字符串格式的IP和大端序IP的互相转换,用于网络通信的服务端程序中
1 2 3 4 5 6 7 8 9 10 typedef unsigned int in_addr_t ; in_addr_t inet_addr (const char * cp) ;int inet_ation (const char * cp, struct in_addr* inp) ;char *inet_ntoa (struct in_addr in) ;
在客户端程序中使用此方法就只能指定ip地址而不能指定域名或者主机号,而用gethostbyname 方法就行
1 2 servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
未封装的客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <unistd.h> #include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> int main (int argc, char * argv[]) { if (argc != 3 ){ std::cout << "Using:./client 服务端IP地址 服务端的端口\n Example:./client 192.168.168.1 5005\n\n" ; return -1 ; } int sockfd = socket (AF_INET, SOCK_STREAM, 0 ); std::cout << sockfd << std::endl; if (sockfd == -1 ){ perror ("socket" ); return -1 ; } struct sockaddr_in servaddr; memset (&servaddr, 0 , sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons (atoi (argv[2 ])); struct hostent * h; if ((h = gethostbyname (argv[1 ])) == nullptr ){ std::cout << "gethostbyname failed.\n" ; close (sockfd); return -1 ; } memcpy (&servaddr.sin_addr, h->h_addr,h->h_length); if (connect (sockfd, (struct sockaddr *)&servaddr, sizeof (servaddr)) == -1 ){ perror ("connect" ); close (sockfd); return -1 ; } char buffer[1024 ]; for (int i = 0 ; i < 10 ; i ++){ int iret; memset (buffer, 0 , sizeof (buffer)); sprintf (buffer, "这是第%d个智鞭,编号%03d。" , i + 1 , i + 1 ); if (iret = send (sockfd,buffer,sizeof (buffer), 0 ) <= 0 ){ perror ("发送" ); return -1 ; } std::cout << "发送" << buffer << std::endl; memset (buffer, 0 , sizeof (buffer)); if (recv (sockfd, buffer, sizeof (buffer), 0 ) <= 0 ){ std::cout << "iret=" << iret << std::endl; break ; } std::cout << "接收" << buffer << std::endl; sleep (1 ); } close (sockfd); return 0 ; }
未封装的服务端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <iostream> #include <unistd.h> #include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> int main (int argc, char * argv[]) { if (argc != 2 ){ std::cout << "Using:./server 通讯端口\n Example:./server 5005\n\n" ; std::cout << "注意:运行服务端的程序Linux系统的防火墙必须要开通5005端口\n" ; std::cout << "如果是云服务器,还有开通云平台的访问策略" ; return -1 ; } int listenfd = socket (AF_INET, SOCK_STREAM, 0 ); if (listenfd == -1 ){ perror ("socket" ); return -1 ; } struct sockaddr_in servaddr; memset (&servaddr, 0 , sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons (atoi (argv[1 ])); servaddr.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (listenfd,(sockaddr *)&servaddr, sizeof (servaddr)) == -1 ){ perror ("bind" ); close (listenfd); return -1 ; } if (listen (listenfd,5 ) == -1 ){ perror ("listen" ); close (listenfd); return -1 ; } int clientfd = accept (listenfd, 0 , 0 ); if (clientfd == -1 ){ perror ("accept" ); close (listenfd); return -1 ; } std::cout << "客户端已连接" << std::endl; char buffer[1024 ]; while (true ){ int iret; memset (buffer, 0 , sizeof (buffer)); if ((iret = recv (clientfd, buffer, sizeof (buffer), 0 )) <= 0 ){ std::cout << "iret= " << iret << std::endl; break ; } std::cout << "接收" << buffer << std::endl; strcpy (buffer, "ok" ); if ((iret = send (clientfd, buffer, sizeof (buffer), 0 ) <= 0 )){ perror ("send" ); break ; } std::cout << "发送:" << buffer << std::endl; } close (listenfd); close (clientfd); return 0 ; }
封装的客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 #include <iostream> #include <unistd.h> #include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <string> class ctcpclient {public : int m_clientfd; std::string m_ip; unsigned short m_port; bool connect (const std::string& in_ip, const unsigned short in_port) { m_clientfd = socket (AF_INET, SOCK_STREAM, 0 ); if (m_clientfd == -1 ){ return false ; } struct sockaddr_in servaddr; m_ip = in_ip; m_port = in_port; servaddr.sin_family = AF_INET; servaddr.sin_port = htons (m_port); struct hostent * h; if ((h = gethostbyname (m_ip.c_str ())) == nullptr ){ close (); m_clientfd = -1 ; return false ; } memcpy (&servaddr.sin_addr, h->h_addr, h->h_length); if (::connect (m_clientfd, (struct sockaddr *) &servaddr, sizeof (servaddr)) == -1 ){ close (); m_clientfd = -1 ; return false ; } return true ; } bool send (const std::string& buffer) { if (m_clientfd == -1 ) return false ; if (::send (m_clientfd, buffer.data (), buffer.size (), 0 ) <= 0 ) return false ; return true ; } bool recv (std::string& buffer,const size_t maxlen) { buffer.clear (); buffer.resize (maxlen); int readn=::recv (m_clientfd, &buffer[0 ], buffer.size (), 0 ); if (readn <= 0 ) { buffer.clear (); return false ; } buffer.resize (readn); return true ; } bool close () { if (m_clientfd == -1 ) return false ; ::close (m_clientfd); m_clientfd = -1 ; return true ; } ctcpclient ():m_clientfd (-1 ){} ~ctcpclient (){close ();} }; int main (int argc, char * argv[]) { if (argc != 3 ){ std::cout << "Using:./client 服务端IP地址 服务端的端口\n Example:./client 192.168.168.1 5005\n\n" ; return -1 ; } ctcpclient tcpclient; if (!tcpclient.connect (argv[1 ], atoi (argv[2 ]))){ perror ("connect" ); return -1 ; } std::string buffer; for (int i = 0 ; i < 10 ; i ++){ buffer = "这是第" + std::to_string (i + 1 ) + "个小智鞭, 编号" + std::to_string (i + 1 ) + "。" ; if (!tcpclient.send (buffer)){ perror ("发送" ); return -1 ; } std::cout << "发送:" << buffer << std::endl; if (!tcpclient.recv (buffer, 1024 )){ perror ("recv" ); break ; } std::cout << "接收:" << buffer << std::endl; sleep (1 ); } tcpclient.close (); return 0 ; }
封装socket的服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 #include <iostream> #include <unistd.h> #include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> class ctcpserver {private : int m_listenfd; int m_clientfd; unsigned short m_port; std::string m_clientip; public : ctcpserver ():m_clientfd (-1 ), m_listenfd (-1 ){} bool initserver (const unsigned short in_port) { if ((m_listenfd = socket (AF_INET, SOCK_STREAM, 0 )) == -1 ) return false ; m_port = in_port; struct sockaddr_in servaddr; memset (&servaddr, 0 , sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons (m_port); servaddr.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (m_listenfd, (const sockaddr *)&servaddr, sizeof (servaddr)) == -1 ){ close (m_listenfd); m_listenfd = -1 ; return false ; } if (listen (m_listenfd, 5 ) == -1 ){ close (m_listenfd); m_listenfd = -1 ; return false ; } return true ; } bool accept () { struct sockaddr_in caddr; socklen_t addrlen = sizeof (caddr); if ((m_clientfd = ::accept (m_listenfd, (struct sockaddr*)&caddr, &addrlen)) == -1 ) return false ; m_clientip = inet_ntoa (caddr.sin_addr); return truei; } const std::string & clientip () const { return m_clientip; } bool send (const std::string& buffer) { if (m_clientfd == -1 ) return false ; if ((::send (m_clientfd, buffer.data (), buffer.size (), 0 )) <= 0 ) return false ; return true ; } bool recv (std::string &buffer, const size_t maxlen) { buffer.clear (); buffer.resize (maxlen); int readn = ::recv (m_clientfd, &buffer[0 ], buffer.size (), 0 ); if (readn <= 0 ){ buffer.clear (); return false ; } buffer.resize (readn); return true ; } bool closelisten () { if (m_listenfd == -1 ) return false ; ::close (m_listenfd); m_listenfd = -1 ; return true ; } bool closeclient () { if (m_clientfd == -1 ) return false ; ::close (m_clientfd); m_clientfd = -1 ; return true ; } ~ctcpserver (){ closelisten (); closeclient (); } }; int main (int argc, char * argv[]) { if (argc != 2 ){ std::cout << "Using:./server 通讯端口\n Example:./server 5005\n\n" ; std::cout << "注意:运行服务端的程序Linux系统的防火墙必须要开通5005端口\n" ; std::cout << "如果是云服务器,还有开通云平台的访问策略" ; return -1 ; } ctcpserver tcpserver; if (!tcpserver.initserver (atoi (argv[1 ]))){ perror ("initserver" ); return -1 ; } if (!tcpserver.accept ()){ perror ("accept" ); return -1 ; } std::cout << "客户端已连接( " << tcpserver.clientip () << ")。\n" ; std::string buffer; while (true ){ if (!tcpserver.recv (buffer, 1024 )){ perror ("recv" ); break ; } std::cout << "接收" << buffer << std::endl; buffer = "ok" ; if (!tcpserver.send (buffer)){ perror ("send" ); break ; } std::cout << "发送:" << buffer << std::endl; } return 0 ; }
多进程的服务端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 #include <iostream> #include <unistd.h> #include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <signal.h> void FatherEXIT (int sig) ;void ChildEXIT (int sig) ;class ctcpserver {private : int m_listenfd; int m_clientfd; unsigned short m_port; std::string m_clientip; public : ctcpserver ():m_clientfd (-1 ), m_listenfd (-1 ){} bool initserver (const unsigned short in_port) { if ((m_listenfd = socket (AF_INET, SOCK_STREAM, 0 )) == -1 ) return false ; m_port = in_port; struct sockaddr_in servaddr; memset (&servaddr, 0 , sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons (m_port); servaddr.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (m_listenfd, (const sockaddr *)&servaddr, sizeof (servaddr)) == -1 ){ close (m_listenfd); m_listenfd = -1 ; return false ; } if (listen (m_listenfd, 5 ) == -1 ){ close (m_listenfd); m_listenfd = -1 ; return false ; } return true ; } bool accept () { struct sockaddr_in caddr; socklen_t addrlen = sizeof (caddr); if ((m_clientfd = ::accept (m_listenfd, (struct sockaddr*)&caddr, &addrlen)) == -1 ) return false ; m_clientip = inet_ntoa (caddr.sin_addr); return true ; } const std::string & clientip () const { return m_clientip; } bool send (const std::string& buffer) { if (m_clientfd == -1 ) return false ; if ((::send (m_clientfd, buffer.data (), buffer.size (), 0 )) <= 0 ) return false ; return true ; } bool recv (std::string &buffer, const size_t maxlen) { buffer.clear (); buffer.resize (maxlen); int readn = ::recv (m_clientfd, &buffer[0 ], buffer.size (), 0 ); if (readn <= 0 ){ buffer.clear (); return false ; } buffer.resize (readn); return true ; } bool closelisten () { if (m_listenfd == -1 ) return false ; ::close (m_listenfd); m_listenfd = -1 ; return true ; } bool closeclient () { if (m_clientfd == -1 ) return false ; ::close (m_clientfd); m_clientfd = -1 ; return true ; } ~ctcpserver (){ closelisten (); closeclient (); } }; ctcpserver tcpserver; int main (int argc, char * argv[]) { if (argc != 2 ){ std::cout << "Using:./server 通讯端口\n Example:./server 5005\n\n" ; std::cout << "注意:运行服务端的程序Linux系统的防火墙必须要开通5005端口\n" ; std::cout << "如果是云服务器,还有开通云平台的访问策略" ; return -1 ; } for (int i = 0 ; i < 65 ; i ++) signal (i, SIG_IGN); signal (SIGTERM, FatherEXIT); signal (SIGINT, FatherEXIT); if (!tcpserver.initserver (atoi (argv[1 ]))){ perror ("initserver" ); return -1 ; } while (true ){ if (!tcpserver.accept ()){ perror ("accept" ); return -1 ; } int pid = fork(); if (pid == -1 ){ perror ("fork" ); return -1 ; } if (pid > 0 ) { continue ; } signal (SIGTERM, ChildEXIT); signal (SIGINT, SIG_IGN); std::cout << "客户端已连接( " << tcpserver.clientip () << ")。\n" ; std::string buffer; while (true ){ if (!tcpserver.recv (buffer, 1024 )){ perror ("recv" ); break ; } std::cout << "接收" << buffer << std::endl; buffer = "ok" ; if (!tcpserver.send (buffer)){ perror ("send" ); break ; } std::cout << "发送:" << buffer << std::endl; } } return 0 ; } void FatherEXIT (int sig) { signal (SIGTERM, SIG_IGN); signal (SIGINT, SIG_IGN); std::cout << "父进程" << getpid () << "正在退出, sig=" << sig << std::endl; kill (0 ,SIGTERM); std::cout << "子进程" << getpid () << "正在释放父进程的资源以及全局资源" << std::endl; tcpserver.closelisten (); exit (0 ); } void ChildEXIT (int sig) { signal (SIGTERM, SIG_IGN); signal (SIGINT, SIG_IGN); std::cout << "子进程" << getpid () << "正在退出, sig=" << sig << std::endl; std::cout << "子进程" << getpid () << "正在释放子进程的资源" << std::endl; tcpserver.closeclient (); exit (0 ); }
注意一定要释放资源,在子进程中关闭监听socket,在父进程中关闭 客户端连接socket,杀死进程时一定要释放资源
void * 是什么?
void * 是一个跳跃力(+1 后跨越的幅度)未定的指针 这就是它的神奇之处了,我们可以自己控制在需要的时候将它实现为需要的类型。这样的好处是:编程时候节约代码,实现泛型编程。
void 是一种指针类型,常用在函数参数、函数返回值中需要兼容不同指针类型的地方。我们可以将别的类型的指针无需强制类型转换的赋值给void 类型。也可以将void *强制类型转换成任何别的指针类型,至于强转的类型是否合理,就需要我们程序员自己控制了。
文件传输 服务端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 #include <iostream> #include <unistd.h> #include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <signal.h> #include <fstream> void FatherEXIT (int sig) ;void ChildEXIT (int sig) ;class ctcpserver {private : int m_listenfd; int m_clientfd; unsigned short m_port; std::string m_clientip; public : ctcpserver ():m_clientfd (-1 ), m_listenfd (-1 ){} bool initserver (const unsigned short in_port) { if ((m_listenfd = socket (AF_INET, SOCK_STREAM, 0 )) == -1 ) return false ; m_port = in_port; struct sockaddr_in servaddr; memset (&servaddr, 0 , sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons (m_port); servaddr.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (m_listenfd, (const sockaddr *)&servaddr, sizeof (servaddr)) == -1 ){ close (m_listenfd); m_listenfd = -1 ; return false ; } if (listen (m_listenfd, 5 ) == -1 ){ close (m_listenfd); m_listenfd = -1 ; return false ; } return true ; } bool accept () { struct sockaddr_in caddr; socklen_t addrlen = sizeof (caddr); if ((m_clientfd = ::accept (m_listenfd, (struct sockaddr*)&caddr, &addrlen)) == -1 ) return false ; m_clientip = inet_ntoa (caddr.sin_addr); return true ; } const std::string & clientip () const { return m_clientip; } bool send (const std::string& buffer) { if (m_clientfd == -1 ) return false ; if ((::send (m_clientfd, buffer.data (), buffer.size (), 0 )) <= 0 ) return false ; return true ; } bool recv (std::string &buffer, const size_t maxlen) { buffer.clear (); buffer.resize (maxlen); int readn = ::recv (m_clientfd, &buffer[0 ], buffer.size (), 0 ); if (readn <= 0 ){ buffer.clear (); return false ; } buffer.resize (readn); return true ; } bool recv (void *buffer, const size_t maxlen) { if (::recv (m_clientfd, buffer, maxlen, 0 ) <= 0 ) return false ; return true ; } bool recvfile (const std::string &filename, const size_t filesize) { std::ofstream fout; fout.open (filename, std::ios::out); if (!fout.is_open ()) { std::cout << "打开文件" << filename << "失败。\n" ; return false ; } int totalbytes = 0 ; int onread = 0 ; char buffer[1000 ]; while (true ){ if (filesize - totalbytes > 1000 ) onread = 1000 ; else onread = filesize - totalbytes; if (!recv (buffer, onread)) return false ; fout.write (buffer, onread); totalbytes += onread; if (totalbytes == filesize) break ; } return true ; } bool closelisten () { if (m_listenfd == -1 ) return false ; ::close (m_listenfd); m_listenfd = -1 ; return true ; } bool closeclient () { if (m_clientfd == -1 ) return false ; ::close (m_clientfd); m_clientfd = -1 ; return true ; } ~ctcpserver (){ closelisten (); closeclient (); } }; ctcpserver tcpserver; struct st_fileinfo { char filename[256 ]; int filesize; }; int main (int argc, char * argv[]) { if (argc != 3 ){ std::cout << "Using:./server 通讯端口 文件存放目录\n Example:./server 5005 /tmp\n\n" ; std::cout << "注意:运行服务端的程序Linux系统的防火墙必须要开通5005端口\n" ; std::cout << "如果是云服务器,还有开通云平台的访问策略" ; return -1 ; } for (int i = 0 ; i < 65 ; i ++) signal (i, SIG_IGN); signal (SIGTERM, FatherEXIT); signal (SIGINT, FatherEXIT); if (!tcpserver.initserver (atoi (argv[1 ]))){ perror ("initserver" ); return -1 ; } while (true ){ if (!tcpserver.accept ()){ perror ("accept" ); return -1 ; } int pid = fork(); if (pid == -1 ){ perror ("fork" ); return -1 ; } if (pid > 0 ) { continue ; } signal (SIGTERM, ChildEXIT); signal (SIGINT, SIG_IGN); std::cout << "客户端已连接( " << tcpserver.clientip () << ")。\n" ; struct st_fileinfo fileinfo; memset (&fileinfo, 0 , sizeof (fileinfo)); if (!tcpserver.recv (&fileinfo, sizeof (fileinfo))){ perror ("recv" ); return -1 ; } std::cout << "收到文件名 (" << fileinfo.filename << ", " << fileinfo.filesize << std::endl; if (!tcpserver.send ("ok" )){ perror ("send" ); return -1 ; } std::cout << "发送OK" << std::endl; if (!tcpserver.recvfile (std::string (argv[2 ]) + "/" + fileinfo.filename, fileinfo.filesize)){ std::cout << "接收文件内容失败" << std::endl; return -1 ; } std::cout << "接收文件内容成功" << std::endl; if (!tcpserver.send ("ok" )){ perror ("send" ); return -1 ; } std::cout << "文件传输完毕\n" ; } return 0 ; } void FatherEXIT (int sig) { signal (SIGTERM, SIG_IGN); signal (SIGINT, SIG_IGN); std::cout << "父进程" << getpid () << "正在退出, sig=" << sig << std::endl; kill (0 ,SIGTERM); std::cout << "子进程" << getpid () << "正在释放父进程的资源以及全局资源" << std::endl; tcpserver.closelisten (); exit (0 ); } void ChildEXIT (int sig) { signal (SIGTERM, SIG_IGN); signal (SIGINT, SIG_IGN); std::cout << "子进程" << getpid () << "正在退出, sig=" << sig << std::endl; std::cout << "子进程" << getpid () << "正在释放子进程的资源" << std::endl; tcpserver.closeclient (); exit (0 ); }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 #include <iostream> #include <unistd.h> #include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <string> #include <fstream> class ctcpclient {public : int m_clientfd; std::string m_ip; unsigned short m_port; bool connect (const std::string& in_ip, const unsigned short in_port) { m_clientfd = socket (AF_INET, SOCK_STREAM, 0 ); if (m_clientfd == -1 ){ return false ; } struct sockaddr_in servaddr; m_ip = in_ip; m_port = in_port; servaddr.sin_family = AF_INET; servaddr.sin_port = htons (m_port); struct hostent * h; if ((h = gethostbyname (m_ip.c_str ())) == nullptr ){ close (); m_clientfd = -1 ; return false ; } memcpy (&servaddr.sin_addr, h->h_addr, h->h_length); if (::connect (m_clientfd, (struct sockaddr *) &servaddr, sizeof (servaddr)) == -1 ){ close (); m_clientfd = -1 ; return false ; } return true ; } bool send (const std::string& buffer) { if (m_clientfd == -1 ) return false ; if (::send (m_clientfd, buffer.data (), buffer.size (), 0 ) <= 0 ) return false ; return true ; } bool send (void *buffer, const size_t maxlen) { if (m_clientfd == -1 ) return false ; if (::send (m_clientfd, buffer, maxlen, 0 ) <= 0 ) return false ; return true ; } bool sendfile (const std::string &filename, const size_t filesize) { std::ifstream fin (filename, std::ios::in) ; if (fin.is_open () == false ){ std::cout << "打开文件" << filename << "失败.\n" ; return false ; } int onread = 0 ; int totalbytes = 0 ; char buffer[1000 ]; while (true ){ memset (buffer, 0 , sizeof (buffer)); if (filesize - totalbytes > 1000 ) onread = 1000 ; else onread = filesize - totalbytes; fin.read (buffer, onread); if (!send (buffer, onread)) return false ; totalbytes += onread; if (totalbytes == filesize) break ; } return true ; } bool recv (std::string& buffer,const size_t maxlen) { buffer.clear (); std::cout << buffer << std::endl; buffer.resize (maxlen); int readn=::recv (m_clientfd, &buffer[0 ], buffer.size (), 0 ); if (readn <= 0 ) { buffer.clear (); return false ; } buffer.resize (readn); return true ; } bool close () { if (m_clientfd == -1 ) return false ; ::close (m_clientfd); m_clientfd = -1 ; return true ; } ctcpclient ():m_clientfd (-1 ){} ~ctcpclient (){close ();} }; int main (int argc, char * argv[]) { if (argc != 5 ){ std::cout << "Using:./client 服务端IP地址 服务端的端口, 文件名,文件大小\n" ; std::cout << "Example:./client 192.168.168.1 5005 aaa.txt 2424\n\n" ; return -1 ; } ctcpclient tcpclient; if (!tcpclient.connect (argv[1 ], atoi (argv[2 ]))){ perror ("connect" ); return -1 ; } struct st_fileinfo { char filename[256 ]; int filesize; }fileinfo; memset (&fileinfo, 0 , sizeof (fileinfo)); strcpy (fileinfo.filename, argv[3 ]); fileinfo.filesize = atoi (argv[4 ]); if (!tcpclient.send (&fileinfo, sizeof (fileinfo))){ perror ("send" ); return -1 ; } std::cout << "发送文件信息的结构体" << fileinfo.filename << "(" << fileinfo.filesize << ")" << std::endl; std::string buffer; if (!tcpclient.recv (buffer, 1024 )){ perror ("recv" ); return -1 ; } if (buffer != "ok" ) std::cout << "服务端没有回复ok\n" << std::endl; if (!tcpclient.sendfile (fileinfo.filename, fileinfo.filesize)){ perror ("sendfile" ); return -1 ; } if (!tcpclient.recv (buffer, 2 )){ perror ("recv" ); return -1 ; } if (buffer != "ok" ) { std::cout << "发送文件内容失败。\n" ; return -1 ; } return 0 ; }
三次握手,四次挥手
yum install net-tools -y; 下载工具包
netstat -na|less 查看套接字状态
三次握手
细节
客户端也有端口号,对于程序员来说,可以不必关心客户端的端口号,所以系统随机分配(socket 通讯中的地址包括ip和端口号,但是习惯地址仅仅指ip地址)
服务端的bind函数,普通用户必须是使用1024以上端口,root任意
listen函数第二个参数+1为已连接队列的大小,一次能接受多少
SYN_RECV状态的连接也称半连接
CLOSED是假象状态,实际不存在
四次挥手
细节
主动断开短端在四次挥手后,socket的状态为 TIME_WAIT状态,将持续2MSL(30s/1min/2min)MSL表示报文在网路中存在的最长时间
如果客户端主动断开连接,TIME_WAIT状态几乎不会造成危害
客户端程序的socket很少
客户端的窗口是随机分配的,不存在重用问题
如果客户端主动断开,有两方面的危害:1)socket没有立即释放;2)端口后只能在2MSL后才能重用 也就是会报这个错误:
解决上述问题办法 在服务端程序中,用setsockopt()函数设置socket的属性(一定要放在bind()前)
1 2 3 int opt = 1 ;setsockopt (m_listenfd, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof (opt));
TCP缓存
系统为每个socket创建了发送缓冲区和接收缓冲区,应用程序调用send()/write()函数发送数据的时候,内核把数据从应用进程拷贝到sokcet的发送缓存中区中;在调用recv()/read()函数接收数据的时候,内核把数据从socket的接收缓冲区拷贝到应用进程中
查看缓冲区的大小 1 2 3 4 5 6 7 8 9 10 int bufsize = 0 ;socklen_t optlen = sizeof (bufsize);gersockopt (sockfd, SOL_SOCKET, SO_SNDBUF, &bufsize, &optlen);std::cout << "Send bufsize=" << buffer << std::endl; getsockopt (sockfd, SOL_SOCKET,SO_RCVBUF,&bufsizem &optlen);std::cout << "recv bufsize =" << bufsize << std::endl;
可能遇到的问题:
send()函数可能会阻塞吗?
会,当发送缓冲区满了和服务端接收满了 就会
向socket中写入数据后,如果关闭了socket,对端还能接收到数据吗?
能,因为这些数据已经存入了对端的接收缓存区中了,即使关闭了socket,之前发送的数据只要还在缓冲区中就能接受
Nagle 算法
IO多路复用 -select模型
什么是IO多路复用
用一个进/线程处理多个TCP连接,减少系统开销
三种模型:select(1024), poll(数千)和epoll(百万)
括号中表示其处理TCP连接的数量
网络通讯-读事件
已连接队列中有已经准备好的socket(有新的客户端连上来)
接收缓存中有数据可以读
tcp连接已断开(对端关闭了链接)
网络通讯-写事件
发送缓冲区没有满,可以写入数据(可以向对端发送报文).
select 的原理 select之于服务器server,相当于秘书之于老板,select是由内核提供的。之前没有select时,server要一直accept()阻塞,等待有客户端的连接。
有了select之后,相当于给各个客户端留电话,谁有事就给秘书打电话,然后秘书告诉老板去调用accept(),创建cfd,和客户端建立连接。
select自己不会创建出cfd,与客户端连接。
所以select做的就是监听,lfd。其实请求连接本质是一个读事件,只是读到的数据是连接请求
select 函数的使用 select函数的声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <sys/select.h> int select (int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) void FD_CLR (int fd, fd_set *set) ;int FD_ISSET (int fd, fd_set *set) ;void FD_SET (int fd, fd_set *set) ;void FD_ZERO (fd_set *set) ;
fd_set的本质如图所示 是一个bitmap(共1024位)
selec服务吨端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 #include <iostream> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/fcntl.h> int initserver (int port) ;int main (int argc, char * argv[]) { if (argc != 2 ){ std::cout << "usage:./tcpselect port\n" ; return -1 ; } int listensock = initserver (atoi (argv[1 ])); std::cout << "listensock= " << listensock << std::endl; if (listensock < 0 ){ std::cout << "initserver() failed.\n" ; return -1 ; } fd_set readfds; FD_ZERO (&readfds); FD_SET (listensock, &readfds); int maxfd = listensock; while (true ){ struct timeval timeout; timeout.tv_sec = 10 ; timeout.tv_usec = 0 ; fd_set tmpfds = readfds; int infds = select (maxfd + 1 , &tmpfds, nullptr , nullptr , &timeout); if (infds < 0 ){ perror ("select() failed" ); break ; } if (infds == 0 ){ perror ("timeout" ); continue ; } for (int eventfd = 0 ; eventfd <= maxfd; eventfd ++){ if (FD_ISSET (eventfd, &tmpfds) == 0 ) continue ; if (eventfd == listensock){ sockaddr_in client; socklen_t len = sizeof (client); int clientsock = accept (listensock, (sockaddr *)&client, &len); if (clientsock < 0 ){ perror ("accept() failed" ); continue ; } printf ("accept client(socket = %d)pk.\n" , clientsock); FD_SET (clientsock, &readfds); if (maxfd < clientsock) maxfd = clientsock; } else { char buffer[1024 ]; memset (buffer, 0 , sizeof (buffer)); if (recv (eventfd, buffer, sizeof (buffer), 0 ) <= 0 ){ printf ("client(eventfd = %d) disconnected.\n" , eventfd); close (eventfd); FD_CLR (eventfd, &readfds); if (eventfd == maxfd){ for (int i = maxfd; i > 0 ; i --){ if (FD_ISSET (i,&readfds)){ maxfd = i; break ; } } } } else { printf ("recv(eventfd=%d)%s\n" , eventfd, buffer); send (eventfd, buffer, strlen (buffer), 0 ); } } } } } int initserver (int port) { int sock = socket (AF_INET, SOCK_STREAM, 0 ); if (sock < 0 ){ perror ("socket() failed" ); return -1 ; } int opt = 1 ; unsigned int len = sizeof (opt); setsockopt (sock, SOL_SOCKET, SO_REUSEADDR,&opt,len); sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons (port); servaddr.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (sock, (sockaddr *)&servaddr, sizeof (servaddr)) < 0 ){ perror ("bind() failed" ); close (sock); return -1 ; } if (listen (sock, 5 ) != 0 ){ perror ("listen() failed" ); close (sock); return -1 ; } return sock; }
select模型的细节 select模型-写事件
如果tcp的发送缓冲区没有满,那么, socket连接是可写的
如果发送数据量太大,或网络带宽不够,发送缓冲区就有填满的可能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 #include <iostream> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/fcntl.h> int initserver (int port) ;int main (int argc, char * argv[]) { if (argc != 2 ){ std::cout << "usage:./tcpselect port\n" ; return -1 ; } int listensock = initserver (atoi (argv[1 ])); std::cout << "listensock= " << listensock << std::endl; if (listensock < 0 ){ std::cout << "initserver() failed.\n" ; return -1 ; } fd_set readfds; FD_ZERO (&readfds); FD_SET (listensock, &readfds); int maxfd = listensock; while (true ){ struct timeval timeout; timeout.tv_sec = 10 ; timeout.tv_usec = 0 ; fd_set tmpfds = readfds; fd_set tmpfds1 = readfds; int infds = select (maxfd + 1 , &tmpfds, &tmpfds1, nullptr , &timeout); if (infds < 0 ){ perror ("select() failed" ); break ; } if (infds == 0 ){ perror ("timeout" ); continue ; } for (int eventfd = 0 ; eventfd <= maxfd; eventfd ++){ if (FD_ISSET (eventfd, &tmpfds) == 0 ) continue ; printf ("eventfd=%d可以写。\n" , eventfd); } for (int eventfd = 0 ; eventfd <= maxfd; eventfd ++){ if (FD_ISSET (eventfd, &tmpfds) == 0 ) continue ; if (eventfd == listensock){ sockaddr_in client; socklen_t len = sizeof (client); int clientsock = accept (listensock, (sockaddr *)&client, &len); if (clientsock < 0 ){ perror ("accept() failed" ); continue ; } printf ("accept client(socket = %d)pk.\n" , clientsock); FD_SET (clientsock, &readfds); if (maxfd < clientsock) maxfd = clientsock; } else { char buffer[1024 ]; memset (buffer, 0 , sizeof (buffer)); if (recv (eventfd, buffer, sizeof (buffer), 0 ) <= 0 ){ printf ("client(eventfd = %d) disconnected.\n" , eventfd); close (eventfd); FD_CLR (eventfd, &readfds); if (eventfd == maxfd){ for (int i = maxfd; i > 0 ; i --){ if (FD_ISSET (i,&readfds)){ maxfd = i; break ; } } } } else { printf ("recv(eventfd=%d)%s\n" , eventfd, buffer); send (eventfd, buffer, strlen (buffer), 0 ); } } } } } int initserver (int port) { int sock = socket (AF_INET, SOCK_STREAM, 0 ); if (sock < 0 ){ perror ("socket() failed" ); return -1 ; } int opt = 1 ; unsigned int len = sizeof (opt); setsockopt (sock, SOL_SOCKET, SO_REUSEADDR,&opt,len); sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons (port); servaddr.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (sock, (sockaddr *)&servaddr, sizeof (servaddr)) < 0 ){ perror ("bind() failed" ); close (sock); return -1 ; } if (listen (sock, 5 ) != 0 ){ perror ("listen() failed" ); close (sock); return -1 ; } return sock; }
select 模型 水平触发
select 模型性能
存在的问题
采用轮询方式扫描bitmap,性能随着socket数量增多而下降
调用一次就需要拷贝一份bitmap
bitmap的大小由FD_SETSIZE宏设置,默认是1024个,可以修改但是效率更低
POLL模型 pollfd 结构体 这个结构体有三个成员,第一个为socket,第二个为请求事件,第三个为返回的事件,并且修改的时候只会修改第三个成员。
poll可要求socket数 其不是固定的,可以由程序员自己决定
pollfd结构体数组初始化的细节 不能用memset 而得用循环 poll 对socket值为-1的会忽略
poll的事件常用 读事件:POLLIN, 写事件:POLLOUT 如果即想读又想写可以使用|来连接
1 2 3 pollfd fds[2048 ]; fds[listensock].fd = POLLIN|POLLOUT
pollfd结构体的使用方法有两种 填在与请求的socket符号编码一样的位置
填在结构体数组最前面 如下图的三行
两者的区别 第一个对于编码来说比较方便,第二个对数组利用率更高
poll模型存在的问题
poll相对于select的区别
(1)应用程序通过poll函数的pollfd结构体将感兴趣的文件描述符和事件类型
(2)调用poll系统调用,将pollfd类型的结构体数组拷贝给内核空间,内核程序采用轮询方式查找所传入的数组中的就绪文件描述符,将内核处理之后的结果通过revents带回,这里的revents中不但有就绪的文件描述符,也有未就绪的,比select有优势的地方是内核并没有直接在传进的数组中修改状态,而是将传入的数组拷贝一份进行修改并由revents带回,所以下一次调用时不需要重新设置要监听的文件描述符;但是采用轮询方式时间复杂度为O(n)
(3)应用程序接受到内核返回的pollfd之后,同样采用轮询方式将events与revents遍历查找,时间复杂度为O(n),找出就绪的文件描述符
poll相对于select的优势
poll所能监听的文件描述符数达到系统允许打开的最大文件描述符数,而select由于_FD_SETSIZE的限制只能达到1023个
poll函数的缺点
(1)应用程序和内核程序都采用轮询方式查找就绪文件描述符,时间复杂度为O(n),
(2)poll仍需要每次调用将文件描述符数组拷贝一份给内核空间
(3)poll之工作在效率低下的LT模式下,在未处理就绪事件时会一直提醒就绪信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 #include <iostream> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <poll.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/fcntl.h> int initserver (int port) ;int main (int argc, char *argv[]) { if (argc != 2 ){ printf ("usage:./tcppoll port\n" ); return -1 ; } int listensock = initserver (atoi (argv[1 ])); std::cout << "listensock=" << listensock << std::endl; if (listensock < 0 ){ perror ("initserver failed" ); } pollfd fds[2048 ]; for (int i = 0 ; i < 2048 ; i ++){ fds[i].fd = -1 ; } fds[listensock].fd = listensock; fds[listensock].events = POLLIN; int maxfd = listensock; while (true ){ int infds = poll (fds, maxfd + 1 , 10000 ); if (infds < 0 ){ perror ("poll failed" ); break ; } else if (infds == 0 ){ perror ("poll timeout" ); continue ; } for (int eventfd = 0 ; eventfd <= maxfd; eventfd ++){ if (fds[eventfd].fd < 0 ) continue ; if (fds[eventfd].events & POLLIN == 0 ) continue ; if (eventfd == listensock){ sockaddr_in client; socklen_t len = sizeof (client); int clientsock = accept (listensock,(sockaddr *) &client, &len); if (clientsock < 0 ){ perror ("accept(*) failed" ); continue ; } printf ("accept client(socket = %d)ok.\n" , clientsock); fds[clientsock].fd = clientsock; fds[clientsock].events = POLLIN; if (maxfd < clientsock) maxfd = clientsock; } else { char buffer[1024 ]; memset (buffer, 0 , sizeof (buffer)); if (recv (eventfd, buffer, sizeof (buffer), 0 ) <= 0 ){ printf ("client(eventfd = %d) disconnected.\n" , eventfd); close (eventfd); fds[eventfd].fd = -1 ; if (eventfd == maxfd){ for (int i = maxfd; i > 0 ; i --){ if (fds[i].fd != -1 ){ maxfd = i; break ; } } } } else { printf ("recv(eventfd = %d).%s\n" , eventfd,buffer); send (eventfd, buffer, strlen (buffer), 0 ); } } } } } int initserver (int port) { int sock = socket (AF_INET, SOCK_STREAM, 0 ); if (sock < 0 ){ perror ("socket() failed" ); return -1 ; } int opt = 1 ; setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof (opt)); sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons (port); servaddr.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (sock, (sockaddr *) &servaddr, sizeof (servaddr)) < 0 ){ perror ("bind() failed" ); close (sock); return -1 ; } if (listen (sock, 5 ) != 0 ){ perror ("listen() failed" ); close (sock); return -1 ; } return sock; }
epoll模型
epoll_create()函数 这个函数是用来创造epoll的,参数为一个整数,这个参数在Linux2.6.8之后被忽略,只要填大于0就行
1 2 int epollfd = epoll_create (1 );
epoll_ctl()函数 epoll_ctl 函数
int epoll_ctl(int epfd, int op, int fd, struct - - epoll_event *event);
功能:epoll 的事件注册函数,它不同于 select() 是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
参数epfd: epoll 专用的文件描述符,epoll_create()的返回值
参数op: 表示动作,用三个宏来表示: EPOLL_CTL_ADD:注册新的 fd 到 epfd 中; EPOLL_CTL_MOD:修改已经注册的fd的监听事件; EPOLL_CTL_DEL:从 epfd 中删除一个 fd;
参数fd: 需要监听的文件描述符
参数event: 告诉内核要监听什么事件,struct epoll_event 结构如: events可以是以下几个宏的集合: EPOLLIN :表示对应的文件描述符可以读(包括对端 SOCKET 正常关闭); EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET :将 EPOLL 设为边缘触发(Edge Trigger)模式,这是相对于水平触发(Level Trigger)来说的。 EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里
返回值:0表示成功,-1表示失败。
epoll_wait函数
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
功能 :等待事件的产生,收集在 epoll 监控的事件中已经发送的事件,类似于 select() 调用。
参数epfd : epoll 专用的文件描述符,epoll_create()的返回值
参数events : 分配好的 epoll_event 结构体数组,epoll 将会把发生的事件赋值到events 数组中(events 不可以是空指针,内核只负责把数据复制到这个 events 数组中,不会去帮助我们在用户态中分配内存)。
参数maxevents : maxevents 告之内核这个 events 有多少个 。
参数timeout : 超时时间,单位为毫秒,为 -1 时,函数为阻塞。
返回值 : 如果成功,表示返回需要处理的事件数目 如果返回0,表示已超时 如果返回-1,表示失败
epoll的相关结构体 epoll_event 1 2 3 4 5 6 7 8 9 10 11 struct epoll_event { uint32_t events; epoll_data_t data; } typedef union epoll_data { void *ptr; int fd; uint32_t ; uint64_t ; }epoll_data_t ;
epoll的视频演示在同级目录下
epoll为什么快?
事件触发模式 事件到来唤醒fd的等待队列中的进程,回调进程之前的注册的回调函数ep_poll_callback ,这个回调函数实际上就是将红黑树上收到event的epitem插入到它的就绪队列中并唤醒调用epoll_wait进程。不用逐个遍历
epoll服务端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <sys/fcbtl.h> #include <sys/epoll.h> int initserver (int port) ;int main (int argc, char *argv[]) { if (argc != 2 ){ printf ("usage:./tcppoll port\n" ); return -1 ; } int listensock = initserver (atoi (argv[1 ])); std::cout << "listensock=" << listensock << std::endl; if (listensock < 0 ){ perror ("initserver failed" ); } int epollfd = epoll_create (1 ); for (int i = 0 ; i < 2048 ; i ++){ fds[i].fd = -1 ; } epoll_event ev; ev.data.fd = listensock; ev.events = EPOLLIN; epoll_ctl (epollfd, EPOLL_CTL_ADD, listensock, &ev); epoll_event evs[10 ]; while (true ){ int infds = epoll_wait (epollfd, evs, 10 , -1 ); if (infds < 0 ){ perror ("poll failed" ); break ; } else if (infds == 0 ){ perror ("poll timeout" ); continue ; } for (int i = 0 ; i < infds; i ++){ if (evs[i].data.fd == listensock){ sockaddr_in client; socklen_t len = sizeof (client); int clientsock = accept (listensock,(sockaddr *) &client, &len); if (clientsock < 0 ){ perror ("accept(*) failed" ); continue ; } printf ("accept client(socket = %d)ok.\n" , clientsock); ev.data.fd = clientsock; ev.events = EPOLLIN; epoll_ctl (epollfd, EPOLL_CTL_ADD, clientsock, &ev); } else { char buffer[1024 ]; memset (buffer, 0 , sizeof (buffer)); if (recv (evs[i].data.fd, buffer, sizeof (buffer), 0 ) <= 0 ){ printf ("client(eventfd = %d) disconnected.\n" , evs[i].data.fd); close (evs[i].data.fd); } else { printf ("recv(eventfd = %d).%s\n" ,evs[i].data.fd,buffer); send (evs[i].data.fd, buffer, strlen (buffer), 0 ); } } } } return 0 ; } int initserver (int port) { int sock = socket (AF_INET, SOCK_STREAM, 0 ); if (sock < 0 ){ perror ("socket() failed" ); return -1 ; } int opt = 1 ; setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof (opt)); sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons (port); servaddr.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (sock, (sockaddr *) &servaddr, sizeof (servaddr)) < 0 ){ perror ("bind() failed" ); close (sock); return -1 ; } if (listen (sock, 5 ) != 0 ){ perror ("listen() failed" ); close (sock); return -1 ; } return sock; }
阻塞I/O 与 非阻塞I/O
阻塞: 在进/线程中,发起一个调用时,在调用返回前,进/线程会被阻塞等待,等待中的进/线让出CPU的使用权
非阻塞: 在进/线程中,发起一个调用时,会立即返回
会阻塞的四个函数:connect(), accept(), send(), recv()
阻塞与非阻塞IO的应用场景
在传统的网络服务端程序中(每连接每线/进程), 采用阻塞IO
在IO复用模型中 是采用非阻塞IO,在事件循环中是不能被阻塞的
非阻塞IO-connect()函数
对非阻塞的IO调用connect()函数,不管是否能连接成功,connect()都会立即返回失败,errno == EINPROGRESS
对非阻塞的IO调用connect()函数后,如果socket的状态是可写的,证明连接是成功的,否则是失败的
对于上面两句话的详细解释
1 2 3 4 5 6 7 8 9 int setnonblocking (int fd) { int flags; if (flags = fcntl (fd, F_GETFL, 0 ) == -1 ){ flags = 0 ; } return fcntl (fd, F_SETTFL, flags|O_NONBKLOCK); }
这样的话怎么判断是否连接成功呢?
需要在连接的地方加入一个判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (connect (sockfd, (sockaddr *)&servaddr, sizeof (servaddr)) != 0 ){ if (errno!=EINPROGRESS){ printf ("connect(%s:%s)failed.\n" , argv[1 ], argv[2 ]); close (sockfd); return -1 ; } } pollfd fds; fds.fd = sockfd; fds.events = POLLOUT; poll (&fds, 1 , -1 );if (fds.revents == POLLOUT) printf ("connect ok\n" );else printf ("connect failed\n" );
非阻塞IO accept()函数
对非阻塞的IO调用accept(), 如果已连接队列中没有socket, 函数立即返回失败, errno == EAGAIN;
1 2 setnonblocking (listensock);
非阻塞IO recv
对非阻塞的IO调用recv(),如果没有数据可读(接收缓冲区为空), 函数立即返回失败, errno == EAGAIN
非阻塞IO send()
对非阻塞IO调用send(), 如果socket 不可写(发送缓冲区已满), 函数立即返回失败,errno==EAGAIN
水平触发与边缘触发
select 和 poll 采用 水平触发
epoll 既有 水平触发 又有 边缘触发 , 默认水平触发
水平触发含义
读事件:如果epoll_wait触发了读事件,表示有数据可读,如果程序没有把数据读完,再次调用epoll_wait的时候,将立即再次触发读事件
写事件: 如果发送缓冲区没有满,表示可以写入数据,此时如果再次调用epoll_wait的时候,将立即再次触发写事件
边缘触发的含义
对于读事件:epoll_wait触发读事件后,不管程序有没有处理读事件,epoll_wait不会再触发读事件,只有新数据到达才会触发,也就是说数据从无到有或者从少到多 的时候,会通知,类似菜鸟驿站
对于写事件:epoll_wait触发读事件后,如果发送缓冲区没满,epoll_wait不会再触发写事件, 只用当发送缓冲区由满 变成不满 时,才再次触发写事件
如果epoll使用边缘触发一定要设置为非阻塞模式
有新客户端连接情况
对于接收发送报文的时候如果使用边缘触发要这么编码,因为这样才能读完
1 2 ev.events = EPOLLOUT|EPOLLET;