在C语言下Socket函数使用

Socket介绍

Socket中文意思是“插座”,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。

既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是文件主要应用于本地持久化数据的读写,而套接字多应用于网络进程间数据的传递。

在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系

套接字通信原理如下图所示:
image

在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。

OSI协议介绍

image

  • 物理层物理层:0101信号传递
  • 数据链路层数据链路层:ppp(3G,4G)协议,交换机,对帧解码并根据中包含信息数据发送到正确的接收方.
  • 网络层:规定通过那个节点,什么网络路径将数据发送到接收方.
  • 传输层传输层:负责数据传输和数据控制,有TCP和UDP协议
  • 会话层:在网络层二个节点建立,维护和终止通信
  • 表达层:把应用程序的抽象语法转换为网络适用的OSI网络测试语法.
  • 应用层应用层:为应用程序提供网络服务.有HTTP协议,FTP协议,SMTP协议,其中ICMP就是我们经常用的ping.ARP和RARP 欺骗主机发送错误的报文,服务器返回物理地址解析IP地址

image
有图知道,socket在应用层和传输层之间。通过TCP协议传输,三次握手有必要知道。三次握手是在listen函数建立握手。

image

TCP三次握手介绍

image

TCP四次挥手介绍

image

关闭close实质是tcp的四次挥手,并不是直接关闭
通道的关闭——四次挥手:
(1)在数据传输完毕之后,客户端会向服务端发出一个FIN终止信号。
(2)服务端在收到这个信号之后会向客户端发出一个ACK确认信号。
(3)如果服务端此后也没有数据发给客户端时服务端会向客户端发送一个FIN终止信号。
(4)客户端在收到这个信号之后会回复一个确认信号,在服务端接收到这个信号之后,服务端与客户端的通道也就关闭了。

Socket编程

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen)调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

image

打开读写关闭通过socket函数来操作

int socket(int domain,int type,int protocol);

参数:
domain:指定发送通信的域
   可取值:AF_UNIX:本地主机通信,与IPC类似
			AF_INET:Internet地址IPV4协议
			AF_INET6:Internet地址IPV6协议
type:指定socket类型
      可取值:SOCK_STREAM(流套接字)SOCK_DGRAM(数据报套接字)、SOCK_RAW(原始套接字)
protocol:指定该套接字描述符上的一个特殊的协议,如TCP,UDP等,一般设为0,会自动选择type的类型对应的协议

补充:SOCK_STREAM(流套接字)应用TCP协议,提供顺序的,可靠的,基于字节流的双向链接
   SOCK_DGRAM(数据报套接字)应用UDP协议,无链接,不可靠,不固定
   SOCK_RAW(原始套接字)提供访问互联网协议和Internal Network Interfaces的权限,只有超级用户才可使用。

bind()函数

绑定socket和端口

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数
	sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
	addrlen:对应的是地址的长度。
	addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,但最终都会强制转换后赋值给sockaddr这种类型的指针传给内核:

通用套接字 sockaddr 类型定义:

typedef unsigned short int sa_family_t;
struct sockaddr 
{ 
 sa_family_t 	sa_family; /* 2字节地址族, AF_xxx */
 char 			sa_data[14]; /*14字节的协议地址 */
}

ipv4对应的是sockaddr_in类型定义:

typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;
struct in_addr 
{
 uint32_t s_addr; 
};

struct sockaddr_in 
{
 sa_family_t 		sin_family; /* 2字节地址族,如:AF_xxx*/
 in_port_t 			sin_port; /* 2字节端口*/
 struct in_addr 	sin_addr; /* 4字节IPv4 地址*/
 unsigned char 		sin_zero[8]; /*8字节未使用的填充数据,总是设置为零 */
};

image

listen()函数

调用socket监听最多连接客户端

int listen(int sockfd, int backlog);

参数
	sockefd: socket()系统调用创建的要监听的socket描述字
	backlog: 相应socket可以在内核里排队的最大连接个数

accept()函数

接收客户端连接请求

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数
	sockfd: 服务器开始调用socket()函数生成的,称为监听socket描述字;
	*addr: 用于返回客户端的协议地址,这个地址里包含有客户端的IP和端口信息等;
	addrlen: 返回客户端协议地址的长度

connect()函数

服务器端连接指定计算机端口

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数
	sockfd: 客户端的socket()创建的描述字
	addr: 要连接的服务器的socket地址信息,这里面包含有服务器的IP地址和端口等信息
	addrlen: socket地址的长度

read() write()函数

读写

close() shutdown()函数

关闭

int close(int fd);
int shutdown(int sockfd, int how);

参数
	fd:要关闭的文件
	how:值为 SHUT_RD 则该套接字不可再读入数据了
		SHUT_WR 则该套接字不可再发送数据了
		SHUT_RDWR 则该套接字既不可以读,也不可以写数据了

网络字节序和主机字节序htons/htonl

意思就是把主机(小端字节序)改为网络(大端字节序)
最常见的有两种

htons:host network short(2字节/16位) ,端口号16位

htonl:host network long(4字节/32位),IP地址32位

1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

参数
	htonl把ip地址主机字节序转换为网络字节序
	htons把端口号主机主机序转换为网络字节序
	
	ntohl把IP地址网络字节顺序到主机字节顺序。
	ntohs把端口号网络字节顺序到主机字节顺序。
   
	INADDR_ANY指定地址为0.0.0.0地址,表示监听所以的IP地址
	hostlong:主机字节顺序表达的32位数
	hostshort:主机字节顺序表达的16位数
	netlong:一个以网络字节顺序表达的32位数
	netshort:一个以网络字节顺序表达的16位数

serv_addr.sin_port = htons(LISTEN_PORT);//端口号转化为网络字节序
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听所有的IP地址

inet_aton(点十转网络)和inet_ntoa(网络转点十进制)

在ipv4进行转换

int inet_aton(const char *string, struct in_addr *addr);
将字符串表示的网络地址转换为该地址数值的整数表示,返回的数字总是按照网络字节顺序的
参数描述:
  1 输入参数string包含ASCII表示的IP地址。
  2 输出参数addr是将要用新的IP地址更新的结构。
返回值:
  如果这个函数成功,函数的返回值非零。如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以他的值会被忽略

char *inet_ntoa(struct in_addr in)
将网络传输的二进制数值转化为成点分十进制的ip地址

返回指向点分十进制字符串的指针。
该函数将一个网络字节顺序的IP地址转换为它所对应的点分十进制串。注意:对inet_aton的调用传递的是指向结构的指针,而对inet_ntoa的调用传递的是结构本身。
ipv4/ipv6网路地址转化函数inet_pton/inet_ntop
int inet_pton(int af, const char *src, void *dst);
参数
	af:可以是AF_INET(对应的是ipv4)或AF_INET6,如果,以不被支持的地址族作为	family参数,这两个函数都返回一个错误,并将errno置为EAFNOSUPPORT.
	src:是一个指向点分十进制串的指针,
	dst:是一个指向转换后的网络字节序的二进制值的指针。
返回值
	若成功则为1,若输入不是有效的表达式则为0,若出错则为-1,并将errno置为EAFNOSUPPORT.


const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
参数:
	dst:参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,调用成功时,这个指针就是该函数的返回值。
size:他是所指向缓存区dst的大小,避免溢出,如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC

通过域名获取IP地址

struct hostent *gethostbyname(const char *hostname);

hostname 为主机名,也就是域名。使用该函数时,只要传递域名字符串,就会返回域名对应的 IP 地址。返回的地址信息会装入 hostent 结构体,该结构体的定义如下:

struct hostent{
    char *h_name;  //官方域名
    char **h_aliases;  //主机的别名.www.google.com就是google他自己的别名
    int  h_addrtype;  //主机ip地址的类型
    int  h_length;  //保存IP地址长度
    char **h_addr_list;  //主机的ip地址
}

从该结构体可以看出,不只返回 IP 地址,还会附带其他信息,各位读者只需关注最后一个成员 h_addr_list。下面是对各成员的说明:

  • h_name:官方域名(Official domain name)。官方域名代表某一主页,但实际上一些著名公司的域名并未用官方域名注册。
  • h_aliases:别名,可以通过多个域名访问同一主机。同一 IP 地址可以绑定多个域名,因此除了当前域名还可以指定其他域名。
  • h_addrtype:gethostbyname() 不仅支持 IPv4,还支持 IPv6,可以通过此成员获取IP地址的地址族(地址类型)信息,IPv4 对应 AF_INET,IPv6 对应 AF_INET6。
  • h_length:保存IP地址长度。IPv4 的长度为 4 个字节,IPv6 的长度为 16 个字节。
  • h_addr_list:这是最重要的成员。通过该成员以整数形式保存域名对应的 IP 地址。对于用户较多的服务器,可能会分配多个 IP 地址给同一域名,利用多个服务器进行均衡负载。

代码实现在下一篇博客中.....

posted @ 2022-10-05 19:51  西故黄鹤楼  阅读(1243)  评论(0编辑  收藏  举报