回忆昨天内容
一、基于TCP的编程实现
模型
实现
服务器端 socket(2) bind(2) listen(2) accept(2)
客户端 socket connect
地址家族
通用地址家族
ipv4地址家族
ipv6地址家族
将代码封装,头文件 源文件 多文件编译链接
今天内容:
一、基于TCP的实现
一次连接,多次通话
客户端和服务器端连接上以后,如果客户端不发送exit,就一直保持着连接。也就是说
客户端发送exit时终止和服务器端的连接。
二、并发服务器的实现
使用多进程实现服务器的并发。
进程模型 一个父进程 多个子进程
父进程负责的任务:
负责受理客户端的连接
cfd=accept(2)
创建子进程
关闭cfd
负责回收子进程的资源
子进程负责的任务:
负责具体的和客户端的沟通
close(sfd)
业务处理
关闭(cfd)
exit(3)
setsockopt(2)
三、基于udp的编程实现
模型:
服务器端
1 创建一个通讯设备 返回该设备的文件描述符(sfd) socket(2)
2 将sfd绑定本地地址和端口 bind(2)
while(1){
接收客户端的数据 recvfrom(2)
处理客户端的数据
响应客户端 sendto(2)
}
客户端模型
1 创建一个通讯设备 返回该设备的文件描述符(sfd)
2 向服务器发送数据 sendto(2)
3 阻塞等待服务器的响应消息 recvfrom(2)
4 处理服务器响应
5 关闭文件描述符 close(sfd)
recvfrom(2) sendto(2)
recvfrom(2)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:从socket上接收一个消息
参数:
sockfd 指定socket 从这个socket上接收消息
buf 存储接收到的结果的地址
len 指定了buf的大小
flags 0
src_addr 存储消息的来源地址
addrlen 值——结果参数 src_addr的尺寸
返回值:成功 返回接收到的字节数
失败 -1 errno被设置
sendto(2)
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:在socket上发送一条消息
参数:
sockfd 指定了文件描述符 向这个文件描述符写数据
buf 要写的数据的地址
len 指定buf中的有效字节数
flags 0
dest_addr 存放目标地址
addrlen 指定目标地址的大小
返回值: 成功 返回发送出去的字节数
失败 -1 errno被设置
服务器端 参见 userver.c
客户端 参见 uclient.c
四、线程的基础
进程 进程是资源分配的基本单位。每个进程都有自己的pid PCB
线程 是执行的基本单位 每个线程有自己的tid 也有自己的TCB
一个进程中可以有多个线程 进程中的线程共享进程的资源 每个线程也有自己的私有资源
线程间的通讯和切换比进程间的通讯和切换开销要小很多
但是注意一个进程中多个线程争抢共享资源
多个线程争抢的共享资源称作 临界资源
如何在一个进程中创建一个线程
pthread_create(3)
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建一个新的线程
参数:
thread 新创建的线程的id存储到这个参数指定的地址空间里
attr NULL 默认属性
start_routine 线程函数
arg 是线程函数的唯一参数
返回值:成功0
错误 非0的错误码 thread未定义
Compile and link with -pthread 编译和链接使用pthread库
void *(*start_routine) (void *)
举例说明 新线程的创建 pthread_c.c
获取自己的tid
pthread_self(3)
#include <pthread.h>
pthread_t pthread_self(void);
Compile and link with -pthread 编译和链接使用pthread库
功能:获取当前线程的tid
参数:无
返回值:总是成功 返回调用的线程的tid
五、线程的终止、汇合、分离
1.线程的终止
1)使用return结束线程函数 不能使用exit(3)
2)使用pthread_exit(3) 终止当前线程
3)可以使用pthread_cancel(3)取消某一个线程
pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
Compile and link with -pthread.
功能:终止当前线程
参数:
retval 通过这个值传递给另外一个线程(同一进程中的线程,该线程调用了pthread_join(3))
返回值: void 不返回给调用者
pthread_cancel(3)
#include <pthread.h>
int pthread_cancel(pthread_t thread);
Compile and link with -pthread.
功能:发送一个取消请求给线程,将其终止 取消线程终止时终止状态标记为PTHREAD_CANCELED,按照int类型访问,输出为-1。代码参见day14,pthread_e.c
参数:
thread 目标线程的tid
返回值:成功 0
错误 非0
pthread_detach(3) 线程的分离
#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.
功能:将线程标记为分离的线程,当其终止时,资源自动释放回系统
参数:
thread 指定要分离的线程tid
返回值:成功 返回0
错误 错误编号
t_net.h
#ifndef T_NET_H_
#define T_NET_H_
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct sockaddr SA;//通用地址
typedef struct sockaddr_in SA4;//ipv4地址
//完成socket bind(2)
int s_bind(int domain,int type,in_port_t port);
int no_accept(int fd);
int h_accept(int fd);
int t_listen(int domain,int type,in_port_t port,int backlog);
#endif
t_net.c
#include<t_stdio.h>
#include"t_net.h"
int s_bind(int domain,int type,in_port_t port){
SA4 serv;//具体ipv4地址
//创建socket 返回该设备的文件描述符
int sfd=socket(domain,type,0);
if(sfd==-1) E_MSG("socket",-1);
//初始化服务器的ip地址和端口
serv.sin_family=AF_INET;
serv.sin_port=htons(port);
serv.sin_addr.s_addr=htonl(INADDR_ANY);//本地所有端口的ip地址
//将sfd绑定到本地地址
int b=bind(sfd,(SA *)&serv,sizeof(serv));
if(b==-1) E_MSG("bind",-1);
return sfd;
}
int no_accept(int fd){//没有来电显示
int cfd=accept(fd,NULL,NULL);
if(cfd==-1) E_MSG("accept",-1);
return cfd;
}
int h_accept(int fd){//有来电显示
SA4 cli;
char ip[32];
socklen_t len;
len=sizeof(SA4);
int cfd=accept(fd,(SA *)&cli,&len);
if(cfd==-1) E_MSG("accept",-1);
printf("%s\n",inet_ntop(AF_INET,&cli.sin_addr,ip,32));
return cfd;
}
int t_listen(int domain,int type,in_port_t port,int backlog){
int fd=s_bind(domain,type,port);
if(fd==-1) return -1;
listen(fd,backlog);
return fd;
}
doit.h
#ifndef DO_IT_H_
#define DO_IT_H_
#include<unistd.h>
#include<ctype.h>
void do_main(int cfd);
#endif
doit.c
#include"doit.h"
void do_main(int fd){
char buf[128];
//从连接描述符里读取客户端发送来的数据到buf中
int r=read(fd,buf,128);
//处理客户端数据
for(int i=0;i<r;i++)
buf[i]=toupper(buf[i]);
//响应客户端
write(fd,buf,r);
return;
}
pthread_c.c
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
//线程执行函数
void *doit(void *arg){
printf("%s,pid=%d\n",(char *)arg,getpid());
printf("tid:%lu\n",pthread_self());
return NULL;
}
int main(void){
pthread_t tid;
//创建新的线程
pthread_create(&tid,NULL,doit,"new");
//这个时候,是几个线程在执行?
//这两个线程是异步的,没有sleep就不确定谁先执行
sleep(1);
doit("main");
return 0;
}
userver.c
#include<t_net.h>
#include<t_stdio.h>
#include<ctype.h>
#include<unistd.h>
int main(){
char buf[256];
SA4 cli;
socklen_t len;
//socket bind
int fd=s_bind(AF_INET,SOCK_DGRAM,4444);
if(fd==-1) return -1;
while(1){
len=sizeof(SA4);
//阻塞等待客户端请求的数据
int rcv=recvfrom(fd,buf,256,0,(SA *)&cli,&len);
if(rcv==-1) E_MSG("recvfrom",-1);
//获取到客户端请求的数据,处理数据
for(int i=0;i<rcv;i++)
buf[i]=toupper(buf[i]);
//将处理结果响应给客户端
sendto(fd,buf,rcv,0,(SA *)&cli,sizeof(SA4));
}
close(fd);
return 0;
}
uclient.c
#include<t_net.h>
#include<t_stdio.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char* argv[]){
char *msg="hello beijing\n";
SA4 serv;
char buf[256];
//创建socket设备 返回该设备的文件描述符
int fd=socket(AF_INET,SOCK_DGRAM,0);
if(fd==-1) E_MSG("socket",-1);
//初始化serv
serv.sin_family=AF_INET;
serv.sin_port=htons(4444);
inet_pton(AF_INET,argv[1],&serv.sin_addr);
//向服务器发送消息
int s=sendto(fd,msg,strlen(msg),0,(SA *)&serv,sizeof(SA4));
if(s==-1) E_MSG("sendto",-1);
//阻塞等待服务器的响应
int rcv=recvfrom(fd,buf,256,0,NULL,NULL);
if(rcv==-1) E_MSG("recvfrom",-1);
//处理响应的消息
write(1,buf,rcv);
//关闭fd
close(fd);
return 0;
}