ch2网络socket编程

ch2网络socket编程

一、socket编程

1.socket简介

socket,一个套接字文件,在传输层、网络层、数据链路层,三者包含的MAC地址、IP地址、端口既可以唯一指定了一台主机的一个程序,MAC与IP指定设备,端口指定主机上的应用程序,socket是应用层与传输层之间的一个抽象层,网络通信一般就要通过这个套接字API进行网络通信

2.socket通信流程图示

在这里插入图片描述

3.socket通信编程详细过程

服务器端:
1.调用socket()函数

先调用socket函数,创建一个套接字,socket返回的是一个socket描述字,实际上与文件描述符一样,起的是指代作用

int socket(int domain,int type,int protocol);//socket函数原型

socket(协议族,socket类型,指定协议)

协议族中:一般使用的是两种协议,一种是AF_INET(ipv4),另外一种是AD_INET6(ipv6)

socket类型:一般使用的是两种类型,一种是面对TCP的SOCK_STREAM(文件流类型),还有一种是面对UDP的SOCK_DGRAM

指定协议:一般该参数传0,让其自行匹配协议

首先创建一个socket套接字:

int 	listen_fd;							//定义一个整型变量来接收socket返回的socket描述字
listen_fd=socket(AF_INET,SOCK_STREAM,0);	//创建一个基于IPV4面向TCP传输的socket套接字
2.调用bind()函数

当我们创建了一个socket套接字后,该套接字接下来要用于网络传输过程,那么我们就需要两个东西,一个是IP地址,一个是端口,bind函数的作用就是将服务器的IP地址与端口与套接字(listen_fd)绑定,便于后续的传输访问

int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);//bind函数原型

bind(套接字,存放服务器IP和端口的结构体指针,结构体的长度)

下面调用bind函数将IP、端口与listen_fd绑定:

#define server_port	9999	   //宏定义监听的服务器端口
memset(&server_addr,0,sizeof(server_addr));//在bind之前对该结构体内的数据进行初始化
server_addr.sin_famliy=AF_INET;//指定IP的协议族为IPV4
server_addr.sin_port=htons(server_port);//指定要绑定的端口
server_addr.sin_addr.s_addr=htols(INADDR_ANY)//指定绑定的ip,该行定义的是该主机下的所有IP都绑定

bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr))
//使用bind函数绑定端口与IP,最后一个接收长度只能用sizeof,用自定义的socklen_t无法通过编译,具体问题未知,如果不深究直接记住就行

下面解释一下上面代码中的一些关键点

在linux下socket有三种sockaddr定义类型

通用类型(结构体):

struct sockaddr{
		sa_family_t	sa_family;
		char		sa_data[4];
}

IPV4类型:

struct sockaddr_in{
		sa_famliy 		sin_family;
		in_port_t		sin_port;
		struct in_addr	sin_addr
}
struct sin_addr{
		uint32 			s_addr;
}

还有一个IPV6类型,一般通信都使用的IPV4,所以IPV6就不再赘述。

通过上面结构体中结构体的定义可以看出,IPV4的sockaddr_in结构体是不能直接放入bind函数中用于绑定

server_addr.sin_famliy=AF_INET;//指定IP的协议族为IPV4
server_addr.sin_port=htons(server_port);//指定要绑定的端口
server_addr.sin_addr.s_addr=htonl(INADDR_ANY)//指定绑定的ip,该行定义的是该主机下的所有IP都绑定

对于这个代码中,对server_addr结构体中进行数据设定,在代码中用到了两个字节序转化函数,htons和htonl,这两个函数是将主机字节序转化为网络字节序,如果不转换字节序的话无法在网络上直接通信(具体原因忘记了),h指代host(主机),n指代(network),to就是转化,s是转换为short型,long是转换为long型,根据端口和IP长度来设定的

bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr))//使用bind函数绑定端口与IP

前文提到通用类型与IPV4类型不匹配,所以在bind中将之前初始化并赋值后的sockaddin类型的结构体内容进行强制转化成通用类型,并且传递这个结构体的首地址给bind,bind就可以找到其内容了,根据类型与结构体首地址,就能解析到IP与端口,根据长度就可以知道多长,取出数据进行绑定即可

3.调用listen()函数

socket套接字是有两个状态,默认是被接收状态,通过调用listen函数,将socket套接字状态转化为接收状态,下文调用accept进行接收准备

int listen(int sockfd,int backlog);//函数原型
listen(listen_fd,13//转化listen_fd状态

sockfd就是要监听的套接字,backlog指代的是可以等待监听的连接队列,不需要太深入了解,设置一个正常数值即可

4.调用accept()函数
int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen);//accept函数原型

sockfd:监听的套接字

addr:接收客户端的端口与地址的结构体指针

addrlen:接受的长度大小

accept是等待服务器端等待客户端连接的一个系统调用函数,其在等待连接时为阻塞状态,监听listen_fd与其绑定的ip与端口,当有客户端连接时,其会返回一个新的文件描述符(我们定义为client_fd),accept参数中的结构体接受客户端的IP与端口,获取客户端的信息

int client_fd;//定义客户端描述符
strcuct sockaddr_in client_addr;//定义接收客户端IP和端口的结构体
socklen_t			*clilen;//定义接受长度
client_fd=accept(listen_fd,(struct sockaddr*)&client_addr,&clilen)
//调用accept函数,等待连接并准备接收客户端ip与端口,最后一个定义接受长度,这个文明传的是取地址符,可以正常根据函数原型传参数,与上面的bind原型有点区别,bind原型传递的长度是不带地址符,但传递失败,只能使用sizeof

当我们连接成功后,我们希望打印客户端的ip与端口信息

printf("客户端的端口为:[%s],ip地址为:[%d]\n",inet_ntoa(client_addr.sin.addr),ntohs(client_addr.sin_port));

解释上面用到的函数:

inet_ntoa:将32位int存储类型的IP地址转化为点分十进制,并打印

ntohs:将网络字节序转换位主机字节序(short型),并打印

5.read(),write(),send(),recv()等IO操作函数

连接成功后服务器端就可以通过client_fd,发送和接收等操作,实现网络socket通信

客户端:
1.调用socket()
int server_fd;
//名字为server_fd,为什么在客户端定义的socket为服务器端的名字呢,因为后续该fd用于指代服务器端,所有对服务器的操作都需要通过此fd

server_fd=socket(AF_INET,SOCK_STREAM,0)//创建socket套接字

创建一个socket套接字准备通信

2.调用connect()

客户端通过调用connect函数与服务器端完成连接,服务器端accept阻塞等待连接

int conne(int sockfd,const struct sockaddr*addr,socklen_t addrlen);//connect函数原型

在调用connect前,我们需要将服务器端的IP地址与端口信息写到sockaddr_in结构体中

#define server_ip "127.0.0.0"					//宏定义服务器端ip地址
#define	server_port 9999		 					//宏定义服务器端端口
struct sockaddr_in server_addr;					//定义一个ipv4类型的地址结构体

memset(&server_addr,0,sizeof(server_addr));		//初始化结构体
server_addr.sin_family=AF_INET;					//协议族定义为IPV4
inet_aton(server_ip,&server_addr.sin_addr);   	//写入访问ip地址,由于定义的地址是点分十进制,所以需要用inet_aton转换存储
server_addr.sin_port=htons(server_port);		//写入访问端口
connect(server_fd,(struct sockaddr*&server_addr,sizeof(server_ip));
//根据写入的服务器端ip与端口连接服务器端,连接成功后server_fd指代服务器端
3.read(),write(),send(),recv()等IO操作函数

连接成功后服务器端就可以通过server_fd,发送和接收等操作,实现网络socket通信

注意read函数是一个阻塞函数,当调用read函数时,如果缓冲区内的数据未准备好时,则会阻塞等待,write一般不会阻塞

二、socket流程总结

1.回顾socket的大致流程是,首先服务器端创建socket套接字,bind绑定ip与端口,listen转换socket状态,accept等待用户连接,再到客户端,客户端创建socket套接字,调用connect连接服务器端,服务器端接到连接请求,同意连接,连接成功,accept返回一个新的文件描述符,用于指代客户端,客户端创建的套接字在connect时相当于绑定端口与ip,使该套接字指向服务器端,因此客户端与服务器都拥有了指向的fd文件描述符,再通过调用文件IO的操作函数即可实现读写等操作,完成网络通信流程

2.close与shutdown

close(int fd)函数是关闭socket(int fd,关闭状态参数)描述字,释放空间,为全关闭状态

shutdown可以自定义关闭状态参数,可以单独关闭读(SHUT_RD),单独关闭写(SHUT_WR),关闭读写(SHUT_RDWR),使用完一个文件描述符后还应该及时关闭文件描述符,释放空间,避免资源浪费

3.socket编程中可以看到用到很多的转化函数,还有之前学的字符串处理函数,可以把一些常用的处理函数记录下来,熟悉用法

函数是关闭socket(int fd,关闭状态参数)描述字,释放空间,为全关闭状态

shutdown可以自定义关闭状态参数,可以单独关闭读(SHUT_RD),单独关闭写(SHUT_WR),关闭读写(SHUT_RDWR),使用完一个文件描述符后还应该及时关闭文件描述符,释放空间,避免资源浪费

3.socket编程中可以看到用到很多的转化函数,还有之前学的字符串处理函数,可以把一些常用的处理函数记录下来,熟悉用法

4.简单的socket只能实现一个客户端的连接,在日常生活中我们需要连接多个客户端,因此就要用到多进程,多线程,IO多用复用解决这个问题,他们各有优缺点,适用的范围不同,后续将对这三个内容进行深入学习

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值