IO模型:2、socket函数基本讲解

23 篇文章 0 订阅

目录

模型阐释

代码示例

关键API

      socket()

af

type

protocol

      bind()

s

addr

namelen

注:

      listen()

s

backlog

参考文章

       accept()

s

addr

addrlen

返回值

        connect()

sockfd

serv_addr

addrlen


引流:Socket编程模型、网络编程模型、IO模型

使用模型

        在编程中最简单最初使用的基本都是阻塞模型,比如fread,fwrite。它仅仅针对当前的线程,且对应的IO操作一定是在函数调用时开始,在完成或者失败时返回。代码逻辑简单、容易理解适合一些简单的场景,例如一些简单的测试用例。

代码示例

        tcp 简单服务端代码


#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include<windows.h>
#include<WinSock2.h>
#include<stdio.h>


#pragma comment(lib,"ws2_32.lib")


int main()
{
	//启动windows socket 2.x 环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);

	//--用socket API建立简易TCP服务端
	// 1、建立一个socket 套接字
	//AF_INET  ipv4网络 ,  SOCK_STREAM 对象为数据流,
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	// 2、绑定用于接受客户端连接的网络端口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;//inet_addr("127.0.0.1");
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		printf("ERROR,绑定用于接受客户端连接的网络端口错误...  \n");
	}
	else {
		printf("绑定端口成功... \n");
	}
	// 3、listen监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		printf("ERROR,listen监听网络端口错误...  \n");
	}
	else {
		printf("listen监听网络端口成功... \n");
	}
	// 4、accept 等待接受客户端连接
	sockaddr_in clientAddr = {};
	int nAddrLen = sizeof(clientAddr);
	SOCKET _cSock = INVALID_SOCKET;

	while (true)
	{
		_cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);
		if (INVALID_SOCKET == _cSock)
		{
			printf("错误,接收到无效客户端SOCKET... \n");
		}
		printf("新客户端加入: IP= %s \n", inet_ntoa(clientAddr.sin_addr));
		// 5、send 向客户端发送一条数据
		char msgBuf[] = "Hello,I'm Server.";
		send(_cSock, msgBuf, strlen(msgBuf) + 1, 0);
	}

	//6、关闭套接字  closesocket
	closesocket(_sock);


	//清除windows socket环境
	WSACleanup();
	return 0;
}

关键API

      socket()

        该API创建了一个套接字描述符,该函数调用完只是得到一个描述符,并未与任何硬件或结构建立关联。调用后系统创建一个套接字,存在于一个名字空间(地址族)中,此时匿名。

SOCKET socket(int af,int type,int protocol);

af

        一个地址描述。仅支持AF_INET格式,也就是说ARPA Internet地址格式。

type

名称 含义
SOCK_STREAM提供面向连接的稳定数据传输,即TCP协议。(TCP)
SOCK_DGRAM使用不连续不可靠的数据包连接。(UDP)
SOCK_RAW提供原始网络协议存取。(udp的以太网数据帧)
SOCK_RDM提供可靠的数据包连接。(可靠UDP即保证交付数据报但不保证顺序)
SOCK_SEQPACKET提供连续可靠的数据包连接。

protocol

名称 含义
IPPROTO_ICMPICMP报文
IPPROTO_TCPTCP传输协议
IPPROTO_UDPUDP传输协议

      bind()

        bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。在connect()listen()调用前使用,connect前可省略,会默认绑定一个很大的端口。

int bind(SOCKET s,struct sockaddr * addr,int namelen);

s

        使用socket()创建出的套接字。

addr

        指向sockaddr结构体类型的指针,该结构使用 SOCKADDR_IN 等效替换。

// 绑定网络通信的地址
SOCKADDR_IN sockAddress;
sockAddress.sin_family = AF_INET;
sockAddress.sin_addr.s_addr = inet_addr("127.0.0.1");	//如INADDR_ANY -任何地址都可以
sockAddress.sin_port = htons(6666);//端口

namelen

        addr结构的长度,可以用sizeof操作符获取。

注:

        当调用此函数成功后,意味着套接字与本机一个准确的网络通信地址建立了绑关系。也可从套接字取出绑定的信息,可使用getsockname()来获知所分配的地址。

      listen()

        listen()是客户端和服务端第一次开始出现不同处理流程的地方,listen()为服务端必定调用的函数。主要作用就是将上述得到的套接字描述子变成一个被动监听的套接字, 用来被动等待客户端的连接。

        创建套接口并为申请进入的连接建立一个后备日志队列(未完成连接队列)已完成连接队列。

        该函数仅仅是通知内核此套接字应如此处理,但进行连接等操作并非该函数完成。是由内核网络相关的服务模块完成,即可以认为该函数仅仅是一个配置设置函数。内核是独立的线程在完成套接字的连接等操作

        因此未完成连接队列和已完成连接队列都是由于内核维护:

        未完成连接队列

【存储着尚未建立连接的套接字】 还未完成tcp三次握手的套接字

         已完成连接队列

【存储着已经完成连接的套接字】 成功进行tcp三次握手的套接字
int listen(SOCKET s,int backlog);

s

        前文用到的套接字。

backlog

        backlog指代上述的内核维护的未完成连接队列已完成连接队列,这两个队列大小之和。

        收到客户端的连接请求之后, 内核创建一个套接字存储在未完成连接队列中, 来进行三次握手建立连接。

        连接建立完成以后, 这个套接字就加到已完成连接队列的队尾, 服务器从已完成连接队列中取走一个, 又空出一个位置, 然后已经完成连接的套接字由补充进来, 就这样实现动态平衡。

        因此如果在 listen 之后不进行 accept , connect 也是会成功返回的, 因为此时连接就已经建立好了。

参考文章

        listen(), connect(), accept() 三者的关系

       accept()

        accept() 函数的作用就是在已完成连接队列中取出一个已经建立好的连接.

        如果这个队列中已经没有已完成连接的套接字, 那么 accept() 就会一直阻塞, 直到取得一个已经建立连接的套接字

SOCKET accept(SOCKET s,sockaddr * addr,int FAR * addrlen);

s

        前文用到的套接字。

addr

        指针,指向一个sockaddr结构缓冲区,用来返回连接者的地址信息。

addrlen

       指针,配合前一个参数,告知调用者获得的数据长度。

返回值

       指代远端连接者的套接字,即服务端要向连接着的客户端发送接收数据则使用这个套接字指代。

        connect()

        connect()用于建立与指定socket的连接。

        通常客户端通过 connect() 函数来向服务端主动发起连接, 但是建立连接也不是这个函数完成的, 而是由内核完成的, 这个函数仅仅是通知内核通过三次握手建立连接, 然后将结果返回给这个函数.

        

这个函数默认会一直阻塞, 直到内核连接建立成功或者超时失败才返回(但一般这个过程很快)

int connect(SOCKET s,sockaddr* name,int namelen);

sockfd

        标识一个套接字。

serv_addr

        套接字s想要连接的主机地址和端口号。

addrlen

       addr结构的长度,可以用sizeof操作符获取。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值