网络编程(API)

网络编程(API)

一.tcp

1.服务器端

1)创建套接字(socket)

	int socket(int domain, int type, int protocol);
 		参数:
 			  domain:协议族,AF_INET
         	  type:通信类型,tcp协议为SOCK_STREAM
              protocal:具体的协议,一般为0,默认协议
              返回值: 文件描述符

在这里插入图片描述
2)将socket返回的文件描述符与本机地址和端口绑定

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	参数:
		sockfd:socket返回的文件描述符
		addr:地址结构指针,存放ip地址和端口编号
		addrlen  地址结构的长度
		返回值 0
	说明:地址结构体为:
		struct sockaddr 
		{
			sa_family_t sa_family;      //地址协议族
			char sa_data[14];           //地址  端口编号
		}
	这么写是为了适用于各个协议,却不能将32位的二进制IP地址和短整
	型数据的端口编号存储到一个字符数组里,所以使用另一个结构体来
	代替它:
		struct sockaddr_in
		{
         	__SOCKADDR_COMMON (sin_family);    //地址协议族
        	in_port_t sin_port;                //端口编号,2byte
       	    struct in_addr sin_addr;          //IPV4地址
       	};  
	注:该结构体在/usr/include/netinet/in.h里已被定义,
		头文件为<netinet/in.h> ;

在这里插入图片描述
注:htons将端口号从主机字节序转化为网络字节序(大端);inet_pton将IP地址按AF_INET协议族转换为网络地址结构,并存入第三个参数中。

3)监听套接字:监听是否有客户端发起连接请求

	int listen(int sockfd, int backlog);
     	参数  sockfd  文件描述符
              backlog 等待队列元素个数
            当客户端向服务器发起连接时,服务器的监听描述符在端口上收到了
            客户端的连 接请求后,并不是立刻建立连接,而是先将请求放在等
            待的队列中,然后从队列中按照先后顺序取出请求,建立连接。
    	返回值  0代表成功

在这里插入图片描述
4)接收tcp连接:服务端接收请求并建立连接

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	参数:sockfd:监听用文件描述符
		  Addr:入参,是连接来的客户端的地址结构
          addrlen   地址结构的长度指针
    返回值:文件描述符 用于进行通信的描述符


5)IO:使用read/write,或者recv/send

6)关闭套接字 close(文件描述符)

2.客户端

1) socket:用于通信
2) connect:主动发起连接

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	参数:sockfd  通信用描述符
     	  addr  目标服务器的地址结构指针
          addrlen   地址结构的长度
	返回值:成功与否 0/非0

3 ) IO

二. udp

1 使用的系统调用
	ssize_t sendto(int socket, void *message, size_t length, 
						int flags, struct sockaddr *dest_addr,
						socklen_t dest_len);
			
	ssize_t recvfrom(int socket, void *buffer, size_t length,
						 int flags, struct sockaddr *address,
						  socklen_t *address_len);
	注意:在没有数据接收时,此函数阻塞
	
	1) 服务端
		recvfrom
			socket	监听描述符
			buffer	接受的数据buf
			length	buf大小
			flags	0
			address	当recvfrom接收到数据时,保存客户端地址信息,sendto时使用此结构体
			address_len	sockaddr结构体大小
			
		sendto
			socket 	监听描述符
			message 发送的数据buf
			length  发送数据大小
			flags 0
			dest_addr 客户端scokaddr_in结构体,recvfrom填充的结构体
			dest_len	sockaddr 结构体大小
	2) 客户端
		sendto
			socket 		描述符
			message 	发送的数据buf
			length  	发送数据大小
			flags 0
			dest_addr 	服务端scokaddr_in结构体,填写服务端地址和端口等信息
			dest_len	sockaddr 结构体大小
		
		recvfrom
			socket		描述符
			buffer		接受的数据buf
			length		buf大小
			flags		0
			address		服务端scokaddr_in结构体,从服务端接受数据   NULL
			address_len	sockaddr结构体体大小    NULL

问题:服务器与客户端通信时,recv/send、read/write、recvfrom/sendto是阻塞函数,如果一个客户端连接上了又不断开,其他客户端就连不上?解决这个问题需要服务器并发;

三. 并发服务器模型

1.多进程模型:为每一个连接的客户端fock一个子进程来进行I/O,但这会很浪费资源,不建议使用。
在这里插入图片描述
代码说明:若有客户端来连接,则为其fork子进程,失败则关闭通信用文件描述符,并重新连接。
在这里插入图片描述
代码说明:成功则在子进程进行I/O操作

特点:
① fork是昂贵的。fork时需要复制父进程的所有资源,包括内存映象等;
② fork子进程后,父子进程间、兄弟进程间的通信需要进程间通信IPC机制,给通信带来了困难;
③ 多进程在一定程度上仍然不能有效地利用系统资源;
④ 系统中进程个数也有限制

2. 多线程模型:为每一个连接的客户端创建一个子线程来进行I/O,这不会消耗那么多资源,但如果有共用的全局变量,需要完成多线程的互斥或同步,这会很复杂。
在这里插入图片描述代码说明:通信用描述符需要传参入子线程用于I/O,但这之前可能又有新客户端来连接,iClient就会改变,从而影响子线程,所以在堆区申请空间将iClient放进去来避免这一问题。
在这里插入图片描述
代码说明:创建线程,以下为线程函数:
在这里插入图片描述
3.I/O多路复用模型:调用函数,创建一个用于监控文件描述符状态的表格,然后轮讯该表格中的所有元素,当某一个描述符有真正的IO请求时,函数返回,判断是哪个文件描述符发生IO请求,就去对该文件描述符进行IO操作。然后将表格恢复成原始状态,继续监控

函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
				fd_set *exceptfds, struct timeval *timeout);
参数:nfds  最大文件描述符+1  为了监控所有文件描述符
      readfds   读文件描述符集合地址   该集合中主要保存文件描述符会发生读操作的可能
      writefds  写文件描述符集合地址
      exceptfds  异常文件描述符集合地址
      timeout   时间溢出  
				NULL:一直阻塞,直到有文件描述符就绪或出错
				时间值为0:仅仅检测文件描述符集的状态,然后立即返回
				时间值不为0:在指定时间内,如果没有事件发生,则超时返回。

返回值:>0  真正有IO操作的文件描述符的个数
        =0  时间溢出但是没有IO
        <0  出错
        
疑问:select并不知道到底是监控中的哪一个描述符状态改变了,该怎么找到那个描述符呢?
	 void FD_ZERO(fd_set *fdset)  清描述符集合
	 void FD_SET(int fd,fd_set *fdset) 将描述符增加到集合中 
	 void FD_CLR(int fd,fd_set *fdset) 将描述符从集合中清除 
	 int FD_ISSET(int fd,fd_set *fdset)  判断描述符是否在集合中

注:内核允许的select函数监控的描述符,最多能够监控1024个:
在这里插入图片描述
优点:

  1. 避免了创建多个线程所耗费的资源以及时间。
  2. 对socket的轮询是内核态的完成,不需要像多线程那样切换需要耗费资源。
    而epoll的实现可以做到性能几乎不受连接数(单单是连接而没有其他的操作)的影响。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值