socket编程小结

一.地址转化函数

1.字符串IP转整数IP

函数:  int inet_aton(const char *cp, struct in_addr *inp);

参数:

  • cp:待转换的字符串IP。
  • inp:转换后的整数IP,这是一个输出型参数。

返回值:如果转换成功则返回一个非零值,如果输入的地址不正确则返回零值。

                

函数:  in_addr_t inet_addr(const char *cp);

参数:  cp:待转换的字符串IP。

        

返回值:如果输入的地址有效,则返回转换后的整数IP;如果输入的地址无效,则返回INADDR_NONE(通常为-1)。

                

函数:   int inet_pton(int af, const char *src, void *dst);

参数说明:

  • af:协议家族。
  • src:待转换的字符串IP。
  • dst:转换后的整数IP,这是一个输出型参数。

返回值说明:

  • 如果转换成功,则返回1。
  • 如果输入的字符串IP无效,则返回0。
  • 如果输入的协议家族af无效,则返回-1,并将errno设置为EAFNOSUPPORT。

                         

2.整数IP转字符串IP

函数:  char *inet_ntoa(struct in_addr in);

参数: in:待转换的整数IP。


返回值:返回转换后的字符串IP。

函数:  const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数:

  • af:协议家族。
  • src:待转换的整数IP。
  • dst:转换后的字符串IP,这是一个输出型参数。
  • size:用于指明dst中可用的字节数。

返回值:如果转换成功,则返回一个指向dst的非空指针;如果转换失败,则返回NULL。

         

 使用说明:

  • 最常用的两个转换函数是inet_addr和inet_ntoa,因为这两个函数足够简单。这两个函数的参数就是需要转换的字符串IP或整数IP,而这两个函数的返回值就是对应的整数IP和字符串IP。
  • 其中inet_pton和inet_ntop函数不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此这两个函数中对应的参数类型是void*。
  • 实际这些转换函数都是为了满足某些打印场景的,除此之外,更多的是用来做某些数据分析,比如网络安全方面的数据分析
     

                 

3.关于inet_ntoa函数 

inet_ntoa函数可以将四字节的整数IP转换成字符串IP,其中该函数返回的这个转换后的字符串IP是存储在静态存储区的,不需要调用者手动进行释放,但如果我们多次调用inet_ntoa函数,就会出现数据覆盖的问题

(1)覆盖测试 

  •  inet_ntoa函数内部只在静态存储区申请了一块区域,因此inet_ntoa函数第二次转换的结果就会覆盖第一次转换的结果。
  • 如果需要多次调用inet_ntoa函数,那么就要及时保存inet_ntoa的转换结果。

                                 

(2)并发场景下的inet_ntoa函数

  • inet_ntoa函数内部只在静态存储区申请了一块区域,用于存储转换后的字符串IP,那么在线程场景下这块区域就叫做临界区,多线程在不加锁的情况下同时访问临界区必然会出现异常情况。并且在APUE(Unix环境高级编程)中,也明确提出inet_ntoa不是线程安全的函数。 
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>

void* Test1(void* arg)
{
	struct sockaddr_in* p = (struct sockaddr_in*)arg;
	while (1){
		char* p1 = inet_ntoa(p->sin_addr);
		std::cout << "p1: " << p1 << std::endl;
		sleep(1);
	}
}

void* Test2(void* arg)
{
	struct sockaddr_in* p = (struct sockaddr_in*)arg;
	while (1){
		char* p2 = inet_ntoa(p->sin_addr);
		std::cout << "p2: " << p2 << std::endl;
		sleep(1);
	}
}


int main()
{
	struct sockaddr_in addr1;
	struct sockaddr_in addr2;
	addr1.sin_addr.s_addr = 0;
	addr2.sin_addr.s_addr = 0xffffffff;
	
	pthread_t tid1 = 0;
	pthread_create(&tid1,nullptr, Test1, &addr1);
	pthread_t tid2 = 0;
	pthread_create(&tid2,nullptr, Test2, &addr2);
	
	pthread_join(tid1,nullptr);
	pthread_join(tid2,nullptr);
	return 0;
}

  • 在centos7上测试时,在多线程场景下调用inet_ntoa函数并没有出现问题,可能是该函数内部的实现加了互斥锁,这就跟接口本身的设计也是有关系的。
  • 多线程环境下更加推荐使用inet_ntop函数进行转换,该函数是由调用者自己提供缓冲区保存转换结果的,可以规避线程安全的问题。

                                

                

                

二.TCP通信协议流程

1.初始化服务器

  • 调用socket,创建文件描述符。
  • 调用bind,将当前的文件描述符和IP/PORT绑定在一起,如果这个端口已经被其他进程占用了,就会bind失败。
  • 调用listen,声明当前这个文件描述符作为一个服务器的文件描述符,为后面的accept做好准备。
  • 调用accept,并阻塞,等待客户端连接到来。

2.建立连接

(1)而客户端在完成套接字创建后,就会在合适的时候通过connect函数向服务器发起连接请求,而客户端在connect的时候本质是通过某种方式向服务器三次握手.

(2)建立连接的过程:

  • 调用socket,创建文件描述符。
  • 调用connect,向服务器发起连接请求。
  • connect会发出SYN段并阻塞等待服务器应答(第一次)。
  • 服务器收到客户端的SYN,会应答一个SYN-ACK段表示“同意建立连接”(第二次)。
  • 客户端收到SYN-ACK后会从connect返回,同时应答一个ACK段(第三次)。
  • 这个建立连接的过程,通常称为三次握手。

(3)连接并不是立马建立成功的,由于TCP属于传输层协议,因此在建立连接时双方的操作系统会自主进行三次协商,最后连接才会建立成功。
                

3.数据交互

(1)连接一旦建立成功并且被accept获取上来后,此时客户端和服务器就可以进行数据交互了。需要注意的是,连接建立和连接被拿到用户层是两码事,accept函数实际不参与三次握手这个过程,因为三次握手本身就是底层TCP所做的工作。accept要做的只是将底层已经建立好的连接拿到用户层,如果底层没有建立好的连接,那么accept函数就会阻塞住直到有建立好的连接。

(2)双方在进行数据交互时使用的实际就是read和write,其中write就叫做写数据,read就叫做读数据。write的任务就是把用户数据拷贝到操作系统,而拷贝过去的数据何时发以及发多少,就是由TCP决定的。而read的任务就是把数据从内核读到用户。

(3)数据传输的过程:

  • 建立连接后,TCP协议提供全双工的通信服务,所谓全双工的意思是,在同一条连接中,同一时刻,通信双方可以同时写数据,相对的概念叫做半双工,同一条连接在同一时刻,只能由一方来写数据。
  • 服务器从accept返回后立刻调用read,读socket就像读管道一样,如果没有数据到达就阻塞等待。
  • 这时客户端调用write发送请求给服务器,服务器收到后从read返回,对客户端的请求进行处理,在此期间客户端调用read阻塞等待服务器端应答。
  • 服务器调用write将处理的结果发回给客户端,再次调用read阻塞等待下一条请求。
  • 客户端收到后从read返回,发送下一条请求,如此循环下去。
     

4.端口连接

(1)当双方通信结束之后,需要通过四次挥手的方案使双方断开连接,当客户端调用close关闭连接后,服务器最终也会关闭对应的连接。而其中一次close就对应两次挥手,因此一对close最终对应的就是四次挥手。

(2)断开连接的过程:

  • 如果客户端没有更多的请求了,就调用close关闭连接,客户端会向服务器发送FIN段(第一次)。
  • 此时服务器收到FIN后,会回应一个ACK,同时read会返回0(第二次)。
  • read返回之后,服务器就知道客户端关闭了连接,也调用close关闭连接,这个时候服务器会向客户端发送一个FIN(第三次)。
  • 客户端收到FIN,再返回一个ACK给服务器(第四次)。
  • 这个断开连接的过程,通常称为四次挥手。

         

5.通讯流程与socket API之间的对应关系

  • 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect会发出SYN段。
  • 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read返回0就表明收到了FIN段。

                 

6.什么叫做链接?

一个服务器有可能在一段时间内有多个客户端链接服务器,一旦链接了服务器那么服务器就要对外提供多种链接,系统中链接足够多的时候系统需要将这些链接管理起来。所谓的面向链接本质上就是创建链接成功之后双方都要为维护该链接在系统级别创建对应的数据结构来保存对应的链接数据,是需要花费时间和空间的。双方为了维护链接是有成本的。UDP不用做,是快一点的。
 


7.为什么要断开连接?

  • 建立连接本质上是为了保证通信双方都有专属的连接,这样我们就可以加入很多的传输策略,从而保证数据传输的可靠性。但如果双方通信结束后不断开对应的连接,那么系统的资源就会越来越少。
  • 因为服务器是会收到大量连接的,操作系统必须要对这些连接进行管理,在管理连接时我们需要“先描述再组织”。因此当一个连接建立后,在服务端就会为该连接维护对应的数据结构,并且会将这些连接的数据结构组织起来,此时操作系统对连接的管理就变成了对链表的增删查改。
  • 如果一个连接建立后不断开,那么操作系统就需要一直为其维护对应的数据结构,而维护这个数据结构是需要花费时间和空间的,因此当双方通信结束后就应该将这个连接断开,避免系统资源的浪费,这其实就是TCP比UDP更复杂的原因之一,因为TCP需要对连接进行管理。

                 

8.TCP和UDP对比(三点)

  • 可靠传输 vs 不可靠传输
  • 有连接 vs 无连接
  • 字节流 vs 数据报

                 

9.三次握手和四次挥手

  • 三次握手:  双方做了哪些工作?    OS底层创建了对应的数据结构。
  • 四次挥手∶双方做了哪些工作?    双方—定要把之前申请的链接释放掉
  • TCP通信双方地位是对等的

                 

10.整体结构            


   

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值