首先是网络编程板块
1。下面展示一些 多线程实现
。
// An highlighted block
父进程:调用accept,回收子进程,利用信号的机制
子进程:处理连接,收发数据
流程:
1.创建socket,得到监听文件描述符lfd————socket
2.将lfd和ip和端口绑定,————bind
3.设置监听————listen
4.进入while(1)
{
阻塞等待新的连接到,得到cfd————accept
//考虑异常打断,过滤errno中的EINTR||ECONNABORTED,继续生成不打断
fork一个子进程,让子进程处理数据
pid=fork;
if(pid<0){//失败退出}
else if(pid>0)
{
//父进程
//因为新的连接在父进程里面并不使用,父进程只负责接受新的连接,因此关闭cfd
close(cdf);
}
else
{
//子进程
//关闭监听文件描述符,子进程只负责收发数据,因此关闭lfd
close(lfd);
while(1)
{
//收发数据
n=read(cfd,buf,sizeof(buf));
//对方关闭连接或者异常发生
if(n<=0){break;}
write(cdf,buf,n);
}
//发送完后子进程over掉,父进程保留,
//关闭cfd链接
close(cdf);
exit(0);
}
}
2.下面展示一些 多线程
。
// An highlighted block
#pragma once
void main(){
1.创建socket
lfd =Socket(AF_INET,SOCK_STREAM,0);
2.绑定结构体
struct sockaddr_in serve;
归零
bzero(&serve.sizeof(serve));
定义ip类型
serve.sin_family=AF_INET;
定义端口
serve.sin_port=htos(8888);
定义IP地址,0.0.0.0,代表所有本地IP,因为有些电脑有多个网卡,但是这个IP可以代表所有网卡的IP
serve.sin_addr.s_addr=htonl(INADDR_ANY);
绑定
Bind(lfd,(struct sockaddr *)&serve,sizeof(serve));
3.设置监听,最多听128个客户端的内容
Listen(lfd,128);
int cfd;
while(1){
4.开始阻塞接收通信socket,如果出错break掉;
cfd=accept();
5.创建子线程
//如果多个客户端同时连上,那么传入的参数只能是最后一个。
int pthread_create(pthread_t *thread, const thread_work *attr,
void *(*start_routine) (void *), void *arg);
//参数:
//thread:线程id,将线程id存入,线程标识符,线程栈的起始地址,输出型参数
//attr:线程属性,NULL,8种选项
//函数指针:新线程执行该函数指针指向的代码,线程回调函数
//arg:传递给函数指针指向的函数的参数,线程回调函数的参数
//返回值:成功返回0,失败返回错误码,如:
//EAGAIN 描述: 超出了系统限制,如创建的线程太多。
//EINVAL 描述: tattr 的值无效。
6.分离线程,使资源独立回收,不会形成僵尸线程,因为linux其实就是利用进程来代替线程,本质上还是进程
pthread_detach(threadID);
}
close(lfd);//如果退出监听状态,则关闭监听描述符;
//关闭线程
pthread_exit();
}
7.线程回调函数
void* thread_work(void * arg){
//获得文件描述符,之前的参数是void*,需要转成int,不要星
int cfd=*(int*)arg;
while(1){
n=read(cfd,buf,sizeof(buf));
if(n<=0){
break;
}
//读完写,但是只能读完之后再写
write(cfd,buf,n);
}
close(cfd);
}
问题:
// A code block
子线程能否关闭lfd?
不能,因为文件描述符是共享而不是复制,关闭后主线程直接崩了,但是子线程还是可以正常使用
主线程能否关闭cfd?
不能,如果close之后,read直接报错,无法继续运行
多个子线程能否共享cfd?
不能,因为如果短时间发两个,那么同时两个连接上后会报端口已经被使用的错误,需要使用端口复用技术避免上述问题,使用以下代码;
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
但是就算是用了之后也有以下问题:
前面连接的cfd会被覆盖,因此前面的连接会无效,只有最后一个连上了,前面的压根没连
解决方法:
使用struct定义,将id及cfd,客户端的信息定义在一个结构体里面, 然后定义结构体数组
struct INFO{
int cfd;//同时也是标志位,如果初始化为-1,那么在其中一个断开连接后,后续连接可以继续使用这个结构体,使用遍历,如果满了直接close(cfd)
pthread_t threadID;//也可以用threadID当标志位
struct sockaddr_in client;//客户端结构体
}
INFO info[100];//定义了100个
3.IP地址转换之二进制和点分十进制
//新版:
int inet_pton(int family, const char *strptr, void *addrptr); //将点分十进制的ip地址转化为用于网络传输的数值格式
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len); //将数值格式转化为点分十进制的ip地址格式
返回值:若成功则为指向结构的指针,若出错则为NULL
//旧版:
把ip地址转化为用于网络传输的二进制数值
int inet_aton(const char *cp, struct in_addr *inp);
inet_aton() 转换网络主机地址ip(如192.168.1.10)为二进制数值,并存储在struct in_addr结构中,即第二个参数*inp,函数返回非0表示cp主机有地有效,返回0表示主机地址无效。(这个转换完后不能用于网络传输,还需要调用htons或htonl函数才能将主机字节顺序转化为网络字节顺序)
in_addr_t inet_addr(const char *cp);
inet_addr函数转换网络主机地址(如192.168.1.10)为网络字节序二进制值,如果参数char *cp无效,函数返回-1(INADDR_NONE),这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理;
将网络传输的二进制数值转化为成点分十进制的ip地址
char *inet_ntoa(struct in_addr in);
inet_ntoa 函数转换网络字节排序的地址为标准的ASCII以点分开的地址,该函数返回指向点分开的字符串地址(如192.168.1.10)的指针,该字符串的空间为静态分配的,这意味着在第二次调用该函数时,上一次调用将会被重写(复盖),所以如果需要保存该串最后复制出来自己管理!
4.粘包问题如何解决:
方案1:
包头+数据
如4为数据长度+数据--->00101234567890,
其中0010标识数据长度,一共10个字节,后米娜1234567890标识具体的10个数据
值得注意的是,记得字节序的转换,只要是数字就要转换,字符串不用转换。
另外,发送端和接收端可以商议为更复杂的报文结构,相当于对方约定的一个协议(接口)
方案2:
结尾追加特殊字符比如\$
方案3:
发送方和接收方约定发送和接收 的字节长度,接收方确定接收长度128个字节就行,如何实现?
```
5.多进程父子进程异步等待回收进程信息
// A code block
5.1 在父进程中bind之后,进入while循环之前,写入以下代码:
linux下:
//创建信号集
sigset_t mask;
//信号集设置为空、
segemptyset(&mask);
//加入子进程结束信号
sigaddset(&mask,SIGCHILD);
//阻塞SIGCHILD信号,因为后面在accept之后的主进程中需要对SIG_CHILD处理函数进行注册,但是我们不希望注册期间子进程结束,然后自动默认处理sigchild信号因此在这个阻塞
//这样一来就算是注册期间子进程结束,也会阻塞等待主进程处理函数注册完成,然后再按照处理函数对子进程结束信号进行处理,从而达到资源回收的目的
sigprocmask(SIG_BLOCK,&mask,NULL)
5.2 再父进程中注册回调函数,处理SIGCHILD信号
//定义信号注册函数对象
struct sigaction act;
//初始化赋值,这一步定义信号处理函数为waitchild;
act.sa_handler=waitchild;
act.sa_flags=0;
//这一步感觉没必要……;
sigemptyset(&act.sa_mask);
//注册信号处理函数,signal和sigaction区别:sigcation可以移植,signal不可以,尽量用sigaction
sigaction(SIGCHILD,&act.NULL);
//解阻塞sigchild信号
sigprocmask(SIG_UNBLOCK,&mask,NULL);
问:
一定要在accept之后的主进程中写这个吗?
答:
个人感觉不需要,因为其实你在fork之后写反而需要考虑到信号和处理函数是否注册的问题,但是如果在之前写,就不需要考虑这个问题
相当于建立了软连接但是子进程还没有启动,因此其实不需要先阻塞然后再解阻塞,而且如果写在accept之后(while里面)会造成以下问题:
多次反复绑定和解阻塞信号,造成资源的浪费和结构的冗余;
5.3定义信号处理函数,信号处理函数在子线程结束发送SIG——CHILD信号后被调用,
void waitchild(int signo){
pid_t wpid;
while(1){
//NULL标识默认处理回收资源操作
//WNOHANG标识非阻塞,与wait本质区别,可以一边等一边执行下面的代码
wpid=waitpid(-1,NULL,WNOHANG);
if(wpid>0){
//处理成功
}
else if(wpid=0||wpid==-1){
//=0,标识子进程还活着,处理失败
//=-1,子进程已经全部没有了
break;
}
}
}