一、socket函数
第一种使用socket函数来创建套接字,函数调用成功将返回套接字句柄。
socket函数接收三个参数。af 、type 、protocol
af:指定套接字使用的地址格式。
type:指定套接字的类型。
protocol:配合type参数使用,用来指定使用的协议类型。
函数执行失败返回INVALID_SOCKET(即-1),可以通过WSAGetLastError函数取得错误代码。
当不使用套接字时,应使用把它关闭,如果不发生错误,函数返回0,发生错误函数返回SOCKET_ERROT。
int closesocket(SOCKET s);
1.AF地址族
AF地址族在ws2def.h头文件里定义。
地址族名称 | 解释 |
---|---|
AF_UNSPEC | 地址族未指定。 |
AF_INET | Internet协议版本4(IPv4)地址族。 |
2.TYPE套接字类型
套接字类型一共有三种,分别是SOCK_STREAM流式套接字,SOCK_DGRAM数据包套接字,SOCK_RAW原始套接字。
类型 | 解释 |
---|---|
SOCK_STREAM | 使用TCP进行提供有连接的可靠传输 |
SOCK_DGRAM | 使用UDP提供无连接的不可靠传输 |
SOCK_RAW | 不使用特定的协议去封装,靠程序自行处理数据以及协议 |
3.PROTOCOL
用于指定通信协议类型,在ws2def.h头文件里被定义。
当type已经明确指定使用TCP或UDP进行工作的时候,protocol参数可以指定为0.
二、套接字绑定到本地IP和端口
使用bind函数为套接字建立连接,能够将套接字句柄(套接字文件描述符)、端口号和本地
ip
绑定到一起。
int bind(
SOCKET s,
const struct sockaddr* name,
int namelen
);
bind函数接收三个参数。s 、name 、namelen
s:套接字句柄
name:本地地址
namelen:name结构体的大小。
函数调用成功则返回0 ,失败返回-1,错误原因存于 errno 中,可以通过WSAGetLastError函数取得错误代码。如果绑定的地址错误,或者端口已被占用,bind 函数一定会报错。
1.SOCKADDR本地地址结构体
Winsock的第一个版本使用sockaddr结构来解决多种协议地址添加的问题,以下是其结构体结构。
typedef struct sockaddr {
ADDRESS_FAMILY sa_family; // Address family.
CHAR sa_data[14]; // Up to 14 bytes of direct address.
} SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;
该结构体的第一个成员sa_family指定了这个地址使用的AF地址族,sa_ data是个杂糅数组存储的数据在不同的地址家族可能不同。所以Winsock对于TCP/IP使用sockaddr_in结构来存储,最后通过强制转换成sockaddr来使用。
2.SOCKADDR_IN TCP/IP地址结构体
该结构体专门用来TCP/IP版本的sockaddr的结构体,其结构体结构如下:
typedef struct sockaddr_in {
ADDRESS_FAMILY sin_family;
USHORT sin_port;
STRUCT IN_ADDR sin_addr;
CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
该结构体有四个成员,分别如下:
sin_family:AF地址族,应为AF_INET。
sin_port:端口号,需要用htons(unsigned short)函数转换。
sin_addr:指定IP地址,需要使用inet_addr()函数进行字符串转换。
sin_zero[8]:空字节,要设为0,用于让其结构与sockaddr结构相同,方便强制转换。
案例:指定端口号代码
sockaddr_in servAddr;
servAddr.sin_port = htons(4567);
案例:指定 IP地址代码
sockaddr_in servAddr;
servAddr.sin_addr.S_un.S_addr = inet_addr("26.186.41.134");
这里只是简单说明一下代码,具体的原理,以及结构体结构会详细说明。
三、监听套接字
WinSock主要使用listen函数进行监听对应套接字,是否有连接等操作,该函数仅应用在支持连接的套接字上。
int listen(
SOCKET s,
int backlog
);
函数的参数有两个,其一是SOCKET设置需要监听的套接字,其二是backlog设置监听队列中允许保持的尚未处理的最大连接数量。如果一个连接请求到来,但队列中的连接的等待数量超过backlog,则客户端将收到WSAECONNREREFUSED的错误。
四、接收连接请求
1.accept()函数
WinSock服务端主要使用accept函数来接收到服务器来的socket连接。
函数原型如下:
SOCKET accept(
SOCKET s,
struct sockaddr* addr,
int* addrlen
);
该函数有三个参数,其一是SOCKET代表listen函数监听的套接字,在其队列中指出第一个到来的未处理套接字连接,并为其创造新的套接字。其二是addr一个指向sockaddr结构的指针,用来取得连接的地址信息。其三是指向参数二长度的指针。
2.connect()函数
WinSock客户端主要使用connect函数来连接到服务器。
函数原型如下:
int connect(
SOCKET s,
struct sockaddr FAR * name,
int namelen
);
该函数有三个参数,其一是SOCKET代表连接服务器使用的客户端套接字。其二是addr一个指向sockaddr结构的指针,用来取得连接的地址信息。其三是指向参数二长度的指针。
五、收发数据
对于流套接字来说,一般使用send()函数发数据,recv函数收数据,二者函数结构很相似。
1.send()函数
主要用于流套接字发送数据。
函数原型如下
int send(
SOCKET s,
char *buf,
int len,
int flags
);
该函数有四个参数,其一是SOCKET代表一个连接的套接字。其二是buf一个字符数组代表发送数据缓存区。其三是len代表缓存区长度。其四flags值通常指定为0。
2.recv()函数
主要用于流套接字接收数据。
函数原型如下
int recv(
SOCKET s,
char* buf,
int len,
int flags
);
该函数有四个参数,其一是SOCKET代表一个连接的套接字。其二是buf一个字符数组代表接收数据缓存区。其三是len代表缓存区长度。其四flags值通常指定为0。
六、TCP服务端和客户端程序通信实例
WinSock编程首先要做的必须是初始化Winsock库,这样才能开始编程使用库里的函数。
关于初始化WinSock库参考博客:
1.服务端
流程思路讲解:
创建一个套接字sListen用于监听,再将套接字绑定到服务器的本地地址(一张网卡地址上 或者 值为INADDR_ANY,相当于0.0.0.0,表示任意地址),然后使用listen监听函数,监听套接字,接着调用accept函数循环接收客户请求,并创建一个新的套接字Socket,服务端接收请求后使用recv函数和send函数收发数据。完成数据交换后,关闭套接字Socket,继续等待新的连接。服务器关闭后使用::closesocket()关闭监听套接字。
2.客户端
流程思路讲解:
创建一个套接字socket用于连接,再将套接字绑定到客户端的本地地址(一张网卡地址上),然后使用connect函数,连接服务器。连接成功后,使用recv函数和send函数收发数据。完成数据交换后,关闭套接字Socket。