网络编程基础

局域网广域网

局域网(LAN)

局域网的缩写是LAN,是一个本地网络,只能实现小范围短距离的网络通信。

广域网(WAN)

广域网的缩写是WAN,是相对于局域网来说的,局域网传输距离比较近,只能是一个小范围的。如果需要长距离传输,局域网是无法架设的

注释:某些教材也将“城域网(MAN)”划入网络分类,本质是介于局域网与广域网之间的产物,界限划分并不明确,如果一定要有区别的话,最大的特点就是三者在覆盖范围上有区别

网络设备

调制解调器 交换机 路由器 集线器

IP地址

概念

IP地址是Internet中主机的标识

Internet中的主机要与别的机器通信必须具有一个IP地址

IP地址为32位(IPV4)或者128位(IPV6)

表示形式:常用点分十进制

网络号/主机号

IP地址=网络号+主机号

网络号:表示是否在一个网段内(局域网)

主机号:表示在本网段内的ID,同一局域网的主机号不能重复

地址划分

主机号的第一个和最后一个都不能被使用,第一个作为网段号,最后一个作为广播地址

IP地址分为A类,B类,C类,D类,E类

两个特殊的IP地址

0.0.0.0:在服务器中,0.0.0.0指的是本机上所有的IPV4地址,如果一个主机有两个IP地址,,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过这两个IP地址都能访问该服务。INADDR_ANY

127.0.0.1:回环地址/环路地址,所有发往该类地址的数据包都应该被loop back,通常作为测试使用

子网掩码

子网掩码:是一个32位的整数,作用是将某一个IP划分为网络地址和主机地址

子网掩码长度是和IP地址长度完全一样 网络号全为1,主机号全为0 子网掩码一旦确定,主机号和网络号的位数就确定了,那么网络号是多少就确定了,同时,此局域网主机号的最大承载量也就确定了

公式:网络号=IP&MASK

网络模型

网络体系结构

常见的两种网络体系结构:TCP/IP与OSI

TCP/IP常见协议

网络接口和物理层:

        ppp:拨号协议(老式电话线上网方式)

        ARP:地址解析协议 IP-->MAC

        RARP:反向地址转换协议 MAC-->IP

网络层:

        IP(IPV4/IPV6):网间互连的协议

        ICMP:网络控制管理协议,ping 命令使用

        IGMP:网络分组管理协议,广播和组播使用

传输层:

        TCP:传输控制协议

        UDP:用户数据报协议

应用层:

        SSH:远程登录会话安全协议

        telnet:远程登录协议

        FTP:文件传输协议

        HTTP:超文本传输协议

        DNS:地址解析协议

        SMTP/POP3:邮件传输协议

TCP和UDP

TCP

TCP(传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信

适用场景:

        适合于对传输质量要求较高的通信

        在需要可靠数据传输的场合,通常使用 TCP 协议

UDP

UDP(用户数据报协议):是一种面向无连接的传输层协议,提供的服务是不可靠的,在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输

适用场景:

        发送小尺寸数据(如对DNS服务器进行IP地址查询时)

        适合于广播/组播式通信中。

socket类型

流式套接字(SOCK_STREAM)TCP

提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。

数据报套接字(SOCK_DGREAM)UDP 提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。

原始套接字(SOCK_RAW) 可以对较低层次协议如IP、ICMP直接访问。

端口号

为了区分一台主机接收到的数据报应该转交给哪个进程来进行处理,使用端口号来区分

TCP端口号与UDP端口号独立

端口号一般由IANA管理 端口用两个字节来标识:0~65535

众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)

1024~5000 操作系统会动态使用

5000~10000:编程中可用的

字节序

小端序:低字节存储在低地址

大端序:高序字节存储在低地址

注意:网络中传输的数据必须使用网络字节序,即大端字节序

//检查主机的字节序
int checkCpu(){
    union w{
        int a;
        char b;
    }
    c.a=1;
    return (c.b==1);
}

大小端序转换

//主机字节序到网络字节序
u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);
//网络字节序到主机字节序
u_long ntohl(ul_ong hostlong);
u_short ntohs(u_short hostshort);

IP地址转换

typedef unint32_t in_addr_t;
struct in_addr{
	in_addr_t s_addr;
};
in_addr_t inet_addr(const char *cp);//从人看的ip地址转为机器使用的32位无符号整数
char *inet_ntoa(struct in_addr in);//从机器到人
//参考代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>

int main(int argv,const char *argv[]){
    //从人看的IP转机器
    in_addr_t addr;
    addr=inet_addr("192.168.3.1");
    printf("addr=0x%x\n",addr);
    
    //从机器转回IP
    struct in_addr ipaddr;
    ipaddrs_addr=addr;
    
    printf("ip=%s\n",inet_ntoa(ipaddr));
}

TCP编程

流程

服务器:

        1.创建流式套接字(socket())------------------------> 有手机

        2.指定本地的网络信息(struct sockaddr_in)----------> 有号码

        3.绑定套接字(bind())------------------------------>绑定手机

        4.监听套接字(listen())---------------------------->待机

        5.链接客户端的请求(accept())---------------------->接电话

        6.接收/发送数据(recv()/send())-------------------->通话

        7.关闭套接字(close())----------------------------->挂机

客户端:

        1.创建流式套接字(socket())----------------------->有手机

        2.指定服务器的网络信息(struct sockaddr_in)------->有对方号码

        3.请求链接服务器(connect())---------------------->打电话

        4.发送/接收数据(send()/recv())------------------->通话

        5.关闭套接字(close())--------------------------- >挂机

API接口

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain,int type,int protocol);
//功能:创建套接字
/*
	参数:
		domain:协议簇
			AF_UNIX,AF_LOCAL:本地通信
			AF_INET: IPV4
			AF_INET6:IPV6
		type:套接字类型
			SOCK_STREAM:流式套接字->唯一对应TCP
			SOCK_DGREAM:数据报套接字->唯一对应UDP
			SOCK_RAW:原始套接字
		protocol:
			domain和type已经确定协议和类型,因此填0即可
	返回值:
		新的套接字描述符(供后面接口使用)
		-1:出错
*/

int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
//功能:绑定套接字
/*
	参数:
		sockfd:套接字描述符
		addr:网络信息结构体的指针
		addrlen:网络信息结构体的大小
	返回值:
		成功:0
		失败:-1
*/
//通用地址结构
struct sockaddr{
    sa_family_t sa_family;
    char sa_data[14];
};
//IPV4协议地址结构
struct sockaddr_in{
    sa_family_t sin_family;//AF_INET
    in_port_t sin_port;//端口号
    struct in_addr sin_addr;//ip地址结构体
};
//ip地址结构体
struct in_addr{
    uint32_t s_addr;//ip地址主机号
};
/*
	特殊的IP地址:
		INADDR_ANY:标识监听本机的所有地址 0.0.0.0
		127.0.0.1:本机回环地址
*/

int listen(int sockfd,int backlog);
//功能:监听套接字(将主动变为被动)
/*
	参数:
		sockfd:套接字描述符
		backlog:监听队列的最大长度
	返回值:
		成功:0
		失败:-1
*/

int accept(int sockfd,struct sockaddr *addr,socklen *addrlen);
//功能:接收客户端的请求,阻塞接口
/*
	参数:
		sockfd:监听套接字
		addr:客户端网络信息结构体的指针
		addrlen:客户端网络信息结构体的大小指针
	返回值:
		成功:已经连接的新客户端的文件描述符
		失败:-1
*/

ssize_t send(int sockfd,const void *buf,size_t len,int flags);
//功能:发送数据
/*
	参数:
		sockfd:套接字描述符
		buf:发送缓冲区的首地址
		len:发送缓冲区的大小
		flags:0//阻塞发送
	返回值:
		成功:成功发送的字节个数
		失败:-1
*/

ssize_t recv(int sockfd,void *buf,size_t len,int flags);
//功能:接收数据
/*
	参数:
		sockfd:套接字描述符
		buf:接收缓冲区的首地址
		len:接收缓冲区的大小
		flags:0//阻塞接收
	返回值:
		成功:成功接收的字节个数
		失败:-1
*/

int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
//功能:请求连接服务器
/*
	参数:
		sockfd:套接字描述符
		addr:服务器网络信息结构体指针
		addrlen:网络信息结构体的大小
	返回值:
		成功:0
		失败:-1
*/

TCP与UDP的异同

TCP和UDP同属于传输层协议

TCP是面向连接的,可靠的,流式套接字通信

UDP是面向无连接的,不可靠的,数据报套接字通信

UDP编程

流程

server:

        1. 创建数据报套接字(socket(,SOCK_DGRAM,))--------------------->有手机

        2.绑定网络信息(bind())----------------------------------->绑定号码(发短信知道发给谁)

        3.接收信息(recvfrom())--------------------------------------->接收短信

        4.关闭套接字(close())---------------------------------------->接收完毕

client:

        1.创建数据报套接字(socket())----------------------------------->有手机

        2.指定服务器的网络信息------------------------------------------->有对方号码

        3.发送信息(sendto())------------------------------------------>发送短信

        4.关闭套接字(close())----------------------------------------->发送完毕

API接口

ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,struct sockaddr *src_addr,socklen_t *addrlen);
//功能:接受数据
/*
	参数:
		sockfd:套接字描述符
		buf:接收缓冲区的首地址
		len:接收缓冲区的大小
		flags:0
		src_addr:发送端的网络信息结构体的指针
		addrlen:发送端的网络信息结构体大小的指针
	返回值:
		成功:接收的字节个数
		失败:-1
		0:对端退出
*/

ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);
//功能:发送数据
/*
	参数:
		sockfd:套接字描述符
		buf:发送缓冲区的首地址
		len:发送缓冲区的大小
		flags:0
		src_addr:接收端的网络信息结构体的指针
		addrlen:接收端的网络信息结构体的大小
	返回值:
		成功:发送的字节个数
		失败:-1
*/

Linux下的IO模式及其特点

阻塞式IO

特点:最简单,最常用,效率低

阻塞I/O模式是最普遍使用的I/O模式,大部分使用的都是阻塞式的I/O 缺省情况下,套接字建立之后所处的模式便是I/O模式 会发生阻塞的函数示例:

        读操作:

                read、recv、recvfrom

                读阻塞->当被读取缓冲区包含数据时,解除读阻塞

        写操作:

                write、send

                写阻塞->当写入数据超出缓冲区大小时会发生写阻塞,当缓冲区满足一定条件时,写阻塞解除

注意: sendto没有写阻塞,原因是udp本身是面向无连接的,因此udp没有发送缓冲区,因此sendto不是阻塞函数 UDP不用等待确认操作,没有实际的发送缓冲区因此,写操作不会阻塞

        其他操作:

                accept、connect

相关函数已经在之前赘述,这里就不再重复叙述了

非阻塞式IO

特点:可以处理多路IO;但是需要进行轮询操作,因此浪费CPU资源

非阻塞式IO并不会等待阻塞程序执行完成,而是会立刻抛出一个错误

其次,一个应用程序使用了非阻塞模式的套接字后,需要使用一个循环来不停地测试文件描述符是否可读

因此应用程序需要不断访问内核来检查I/O操作是否就绪,这是一个很浪费CPU资源的过程

一般不会使用非阻塞式I/O进行操作

非阻塞式IO的工作模式

如何设置非阻塞模式

方式一:通过函数参数进行设置

//recv函数的最后一个参数为0时,证明此函数为阻塞式IO,当参数为MSG_DONTWAIT表示非阻塞
//示例:
recvbyte=recv(acceptfd,buf,sizeof(buf),MSG_DONTWAIT);
//非阻塞的含义表示需要循环检测数据是否被传递,需要进行轮询操作,机器浪费CPU资源

 方式二:通过fcntl函数进行设置

int fcntl(int fd,int cmd,.../*arg*/);
//功能:设置文件描述符的属性
/*
	参数:
		fd:文件描述符
		cmd:功能选择
			状态属性:
				F_GETFL->获取文件描述符原来的属性
				F_SETFL->设置文件描述符属性
		arg:根据cmd决定是否填充值 int
	返回值:
		成功:F_GETFL:返回值的文件描述符号属性的值int
			F_SETFL 0
		失败:-1
*/

简单案例

#include <stdio.h>
#include <nistd.h>
#include <fcntl.h>

int main(int argc,const char * argv[]){
	int flags=fcntl(0,F_GETFL);
    flags|=O_NONBLOCK;
    fcntl(0,F_SETFL,flags);
}

信号驱动IO

特点:异步通知模式,但是需要底层驱动的支持,部分操作系统可能不支持

通过信号的方式,当内核检测到设备的数据后,主动给应用程序发送SIGIO信号

应用程序收到信号后进行异步处理

应用程序需要把自己的进程号告诉给内核,并打开异步通知机制

//这是一个模板
//将APP进程号告诉给驱动程序
fcntl(fd,F_SETOWN,getpid());//设置程序具有异步的权限
//开通异步通知
int flag=fcntl(fd,F_GETFL);
flag|=O_ASYNC;//这里的标识不唯一,可以将O_ASYNC替换为FASYNC标识
fcntl(fd,F_SETFL,flag);

signal(SIGIO,handler);//当io操作产生时便会触发,这里的handler是一个函数

IO多路复用

IO多路复用机制

应用程序中同时处理多路输入输出流,若采用阻塞模式,将达不到预期的目的;若采用非阻塞模式对多个输入进行轮询,但又太浪费CPU时间;若设置多个线程/进程,分别处理一条数据通路,将产生进程/线程的同步通信问题,使问题变得复杂

比较好的方法是使用I/O多路复用技术,IO复用技术的基本思想:

先构造一张有关描述符的表,然后调用一个函数

当这些文件描述符中的一个或者多个已经准备好IO时,函数才返回 函数返回时告诉进程那个描述符已经就绪,可以进行IO操作

基本流程

先构造一张有关文件描述符的表(集合、数组)

将你关心的文件描述符加入到这个表中

然后调用一个函数 select/poll

当这些文件描述符中的一个或多个已准备好进行IO操作时,该函数返回

判断哪一个或者哪些文件描述符产生了事件(IO操作)

做对应的逻辑处理

实现IO多路复用的方式及其函数功能(函数接口)

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
//功能:select 用于检测是哪个或者哪些文件描述符产生的事件
/*
	参数:
		nfds:检测的最大文件描述符个数
		readfds:读事件集合//一般操作都是读操作
		writefds:写事件集合//NULL标识不关心
		exceptfds:异常事件集合
		timeout:超时检测1	如果不做超时检测:传递NULL
	返回值:
		<0:表示出错
		>0:表示有事件产生
		如果设置了超时检测时间&tv
			<0:出错
			>0:有事件产生
			=0:超时时间已到
		struct timeval{
			long tv_sec;//seconds
			long tv_usec;//microseconds
		};
*/

void FD_CLR(int fd,fd_set *set);//将fd从表中清除
int FD_ISSET(int fd,fd_set *set);//判定fd是否在表中
void FD_SET(int fd, fd_set *set);//将fd添加到表中
void FD_ZERO(fd_set *set);//清空表

/*
	注:select一旦返回会将没有产生事件的文件描述符从表中清空。下次检测需要重新添加。表中的下标是文件描述符的值,添加的时候是根据对应位置添加的,检测个数是添加进表中最大的文件描述符+1
*/

总结select实现IO多路复用的特点

1.一个进程最多只能监听1024个文件描述符(千级别)//可以修改,但是不推荐

2.select每次被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低(消耗CPU资源)

3.select每次都会清空表,每次都需要拷贝用户空间的表到内核空间,效率低(一个进程0~4G,0~3G是用户态,3~4G是内核态,拷贝是非常耗时的)

代码步骤示例

fd_set readfd,tempfd;
FD_ZERO(&readfd);
FD_SET(0&readfd);
FD_SET(sockfd,&readfd);
int max=sockfd;
while(1){
	tempfd=readfd;
	select(max+1,&tempfd,NULL,NULL,NULL);
	if(FD_ISSET(0,&tempfd)){
	
	}
	if(FD_ISSET(sockfd,&tempfd)){
	
	}
}

poll实现IO多路复用

函数接口

int poll(struct pollfd *fds,nfds_t nfds,int timeout);
//功能:监视并等待多个文件描述符的属性变化
/*
	参数:
		fds:关心的文件描述符结构体
		nfds:文件描述符个数
		timeout:超时检测  毫秒级  -1阻塞
	返回值:
		如果不做超时检测:-1
			<0:出错
			>0:表示有事件产生
		如果设置了超时检测
			<0:出错
			>0:表示有事件产生
			=0:表示时间已到
*/
//pollfd结构体
struct pollfd{
    int fd;//检测的文件描述符
    short events;//检测事件
    short revents;//调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员,只需要判断这个成员的值就可以确定是否产生了事件
}
//事件:
	POLLIN//读事件
    POLLOUT//写事件
    POLLERR//异常事件
//注意:一般监听消息都是读消息,终端输入也属于读消息,accept也属于读取与客户端之间的连接

poll实现IO多路复用的特点:

1.优化文件描述符个数的限制(根据poll函数第一个函数的参数来定,如果监听的事件为1个,则结构体数组元素个数为1个,由程序员自己来确定)

2.poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低

3.poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

代码步骤示例

//创建表
struct pollfd fds[N]={};
//将关心的文件描述符添加到表中
fds[0].fd=0
fds[0].events=POLLIN;//读事件

fds[1].fd=1;
fds[1].events=POLLIN;

int num=2;
int ret;
while(1){
	//调用poll函数循环检测是否有事件产生
	ret=poll(fds,num,-1);//阻塞等待事件产生返回
	
	if(fds[0].revents==POLLIN){
	
	}
	
	if(fds[1].revents==POLLIN){
	
	}
}

epoll实现IO多路复用

epoll实现机制

epoll的提出:它所支持的文件描述符上限是系统可以最大打开的文件数目

每个fd上面有callback(回调函数)函数,只有活跃的fd才会主动调用callback函数,不需要操作系统轮询

注意:epoll处理高并发,百万级,无需关心底层怎样实现

 函数接口

#include <sys/epoll.h>

int epoll_create(int size);
//功能:创建红黑树根节点
/*
	参数:
		size:不作为实际意义值>0即可
	返回值:
		成功:返回epoll文件描述符
		失败:-1
*/

int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
//功能:控制epoll属性
/*
	参数:
		epfd:epoll_create的返回句柄
		op:表示动作类型。有三个宏来表示:
			EPOLL_CTL_ADD:注册新的fd到epfd中
			EPOLL_CTL_MOD:修改已注册的fd的监听事件
			EPOLL_CTL_DEL:从epfd中删除一个fd
		fd:需要监听的fd
		event:告诉内核需要监听什么事件
			EPOLLIN:表示对应文件描述符可读
			EPOLLOUT:可写
			EPOLLPRI:有紧急数据可读
			EPOLLERR:错误
			EPOLLHUP:被挂断
		触发方式:边沿触发(默认使用)
		ET模式:表示状态的变化
	返回值:
		成功:0
		失败:-1
*/
//结构体
typedef union epoll_data{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data_t;

struct epoll_event{
    uint32_t events;/*epoll事件*/
    epoll_data data;/*用户数据变量*/
};

int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
//功能:等待事件的产生,类似于select的使用方法
/*
	参数:
		epfd:句柄
		events:用来保存从内核得到事件的集合
		maxevents:表示每次能处理的事件最大个数
		timeout:超时时间,毫秒,0,立即返回,-1阻塞
	返回值:
		成功:返回发生事件的文件描述符个数
		失败:-1
		超时:0
*/

注意:

1.epoll可以同支持边缘触发和水平触发,理论上边缘触发性能更高,但是代码实现复杂

2.epoll同样只告知那些有序的文件描述符,放调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是代表一个就绪描述符数量的值

3.epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个描述符,一旦某个基于文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知

epoll实现IO多路复用的特点

监听的最大文件描述符没有个数限制 

异步IO,当epoll有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接拿到唤醒的文件描述符,不需要轮询,效率高

epoll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

代码步骤示例

struct epoll_event event;//暂时保存关心的文件描述符及对应事件
struct epoll_event revents[20];//保存从链表中拿出来已经产生的事件
//创建表(创建红黑树)
int epfd=epoll_create(1);
if(epfd<0){
    perror();
}
//将关心的文件描述符添加到树上
event.data.fd="文件描述符"
event.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,"文件描述符",&event);

event.data.fd="文件描述符"
event.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,"文件描述符",&event);

int ret;
char buf[128];
int recvbyte;
while(1){
    //epoll_wait 阻塞等待链表中有事件产生,拿事件处理
    ret=epoll_wait(epfd,revents,20,-1);
    if(ret<0){
        perror();
    }
    
    for(int i=0;i<ret;i++);
    {
        if(revents[i].data.fd=="文件描述符"){
            
        }else if(revents[i].data.fd=="文件描述符"){
            
            event.data.fd=acceptfd;
            event.events=EPOLLIN|EPOLLET;
            epoll_ctl(epfd,EPOLL_CTL_ADD,"文件描述符",&event);
        }else{
            if(……){
                perror();
            }else if(……){
                close(event[i].data.fd);
				epoll_ctl(epfd,EPOLL_CTL_DEL,revents[i].data.fd,NULL);
            }
        }
    }
}

服务器模型

在网络程序里面,通常都是一个服务器处理多个客户机

为了处理多个客户机请求,服务器端的程序有不同的处理方式

循环服务器模型

同一个时刻只能响应一个客户端的请求

伪代码

socket()
bind()
listen()
while(1){
	accept()
	while(1){
		process()
	}
	close()
}

并发服务器模型

同一时刻可以响应多个客户端的请求,常用的模型有多进程模型/多线程模型/IO多路复用模型

多进程模型

每来一个客户端连接,开一个子进程来专门处理客户端的数据,实现简单,但是系统开销相对较大,更推荐使用线程模型

伪代码

socket()
bind()
listen()
while(1){
	accept()
	if(fork()==0){
		while(1){
			process()
		}
		close(clientfd)
		exit()
	}else{
	
	}
}
//注意:收到客户端消息后,打印是来自那个客户端的数据,使用SIGCHLD来处理子进程结束的信号,信号函数中回收进程资源(子进程状态发生改变,会给父进程发送SIGCHLD信号)

多线程模型

每来一个客户端进行连接,开一个子线程来专门处理客户端的数据,实现简单,占用资源少,属于使用比较广泛的模型

伪代码

socket()
bind()
listen()
while(1){
	accept()
	pthread_create()
}

IO多路复用模型

借助select、poll、epoll机制,将新连接的客户端描述符增加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,不过代码写起来会比较繁琐

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值