基于Windows的Socket模型

Socket简介

1. 什么是Socket

所谓socket通常也称作“套接字”。它不是一种网络协议,只是一个网络编程接口,用于描述IP地址和端口,可以通过它访问很多种网络协议,可以将其当作一些协议的封装。
1.1 定义

typedef unsigned int u_int;
typedef u_int SOCKET;

1.2 类型
在这里插入图片描述
1)流式套接字(stream socket)

#define SOCK_STREAM 1

流式套接字提供了双向,有序的,无重复的以及无记录边界的数据流服务,适合处理大量数据。它是面向联结的,必须建立数据传输链路,同时还必须对传输的数据进行验证,确保数据的准确性。因此,系统开销大。
2) 数据报套接字(datagram socket)

#define SOCK_DGRAM 2

数据报套接字也支持双向的数据流,但不保证传输数据的准确性,但保留了记录边界。由于数据报套接字是无联结的,例如广播时的联结(只管向接收方发送数据,不用管接收端是否能够完完全全的接收到),所以并不保证接收端是否正在侦听。数据报套接字传输效率比较高。
3)原始套接字(raw-protocol-interface)

#define SOCK_RAW 3

SOCK_RAW 与SOCK_STREAM、SOCK_DGRAN的区别在于,SOCK_RAW直接置”根“于操作系统网络核心(Network Core)。而SOCK_STREAM、SOCK_DGRAM则”悬浮“于TCP和UDP协议的外围。SOCK_RAW保存了数据包的完整IP头,前面两种套接字只能收到用户数据。因此可以通过SOCK_RAW对数据进行分析。
1.3 Socket开发所必须需要的头文件。
以WinSock V2.0为例
头文件: Winsock2.h
库文件: WS2_32.LIB
动态库:W32_32.DLL

2. 为什么要用Socket

应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(socket)的接口以区分不同的应用程序进程间的网络通信和连接
要通过互联网进行通信,至少需要一对套接字,一个运行于客户机端,另一个运行于服务器端。根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
1. 服务器监听
是服务器端套接字并不定位具体的客户端套接字,而不是等待连接的状态,实时监控网络状态。
2. 客户端请求
由客户端的套接字提出请求,要连接的目标是服务端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就像服务器端套接字提出连接请求。
3. 连接确认
当服务器端套接字监听到或者说是接受到客户端套接字的连接请求,它就响应客户端套接字请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

3. Winsock简介

Windows Sockets 是定义介于 Windows TCP/IP application Client 与 TCP/IP 的 protocol stack 之间的一项标准
界面。它以 U.C. Berkeley 大学 BSD UNIX 中流行的 Socket 接口为范例定义了一套在 Micosoft Windows 下网络编程接口,
它不仅包含了 Berkeley Socket 风格的库函数,也包含了一组针对 Windows 的扩展库函数。Windows 应用程序可以使用
SDK 提供的 Winsock 功能加入网络数据通信的功能。
Winsock 的版本及所支持的系统:
1> Winsock1.1 支持的系统只有 Windows95 和 Windows CE
2>Winsock2.2 支持的系统有 Windows98(包括)及其以上版本

4.Winsock中的一些重要定义

4.1数据类型的基本定义

typedef unsigned char u_char;
typedef unsigned char short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;

4.2网络地址的数据结构

unsigned long addr;

4.3 套接字的数据结构
分为sockaddr结构和sockaddr_in结构,区别在于sockaddr结构是通用socket地址结构,sockaddr_in结构是专门针对Internet域的socket地址结构。
sockaddr结构

struct sockaddr{
	u_short  sa_family;   /* address family */
	char     sa_data[14]; /* up to 14 bytes of direct address */
};

sa_family 为网络地址类型,一般为AF_INET,表示该socket在Internet域中进行通信。
sockaddr_in结构

struct sockaddr_in{
	short    		 sin_family;
	u_short  		 sin_port;
	struct in_addr   sin_addr;
	char             sin_zero[8];
}

sin_family 为网络地址类型。必须设定为 AF_INET.
sin_port 为服务端口
sin_addr 为一个 unsigned long 的 IP 地址
sin_zero 为填充字段,纯粹用来保证结构的大小。
注意:不要使用自己固定的服务端口。如HTTP的端口80等,如果端口设置为0,则系统会自动分配一个唯一端口。
将常用的用点分开德IP地址转换为unsigned long类型的IP地址的函数:

unsigned long inet_addr(const char FAR *cp)
用法:unsigned long addr = inet_addr("192.1.8.84")

如果将sin_addr设置为INADDR_ANY,则表示所有的IP地址,也即所有的计算机。
#define INADDR_ANY (u_long)0x00000000

4.4主机地址的数据结构

struct hostent{
	char FAR *h_name;   			/* official name of host */
	char FAR *FAR * h_aliases;  	/* alias list */
	short h_addrtype;     			/* host address type */
	short h_length;       			/* length or address */
	char FAR * FAR * h_addr_list;   /* list of addresses */
	#define h_addr h_addr_list[0]   /* address, for backward compat */
};

h_name为主机名字。
h_aliases为主机别名列表。
h_addrtype为地址类型
h_length为地址类型
h_addr_list为IP地址,如果该主机有多个网卡,就包括地址的列表。

4.5常见的TCP/IP协议的定义

#define IPPROTO_IP  0
#define IPPROTO_ICMP 1
#define IPPROTO_IGMP 2
#define IPPROTO_TCP  6
#define IPPROTO_UDP  17
#define IPPROTO_RAW  255

5.Winsock的属性

5.1属性内容
允许调试输出

#define SO_DEBUG 0x0001     /* turn on debugging info recording */

是否监听模式

#define SO_ACCEPTCONN 0x0002  /* socket has had listen() */

套接字与其他套接字的地址绑定

#define SO_REUSEADDR 0x0004   /* allow local address reuse */

保持连接

#define SO_KEEPALIVE 0x0008 /* keep connections alive */

不要路由出去

#define SO_DONTROUTE 0x0010 /* just use interface addresses */

设置为广播

#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */

使用环回不通过硬件

#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */

当前拖延值

#define SO_LINGER 0x0080 /* linger on close if data present */

是否加入带外数据

#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */

禁用 LINGER 选项

#define SO_DONTLINGER (int)(~SO_LINGER)

发送缓冲区长度

#define SO_SNDBUF 0x1001 /* send buffer size */

接收缓冲区长度

#define SO_RCVBUF 0x1002 /* receive buffer size */

发送超时时间

#define SO_SNDTIMEO 0x1005 /* send timeout */

接收超时时间

#define SO_RCVTIMEO 0x1006 /* receive timeout */

错误状态

#define SO_ERROR 0x1007 /* get error status and clear */

套接字类型

#define SO_TYPE 0x1008 /* get socket type */

5.2读取属性

int getsockopt (SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen)
s  为欲读取属性的套接字。
level 为套接字选项的级别,大多数是特定协议和套接字专有的。如 IP 协议应为 IPPROTO_IP。
optname 为读取选项的名称
optval  为存放选项值的缓冲区指针。
optlen  为缓冲区的长度
用法:
int ttl = 0; //读取 TTL 值
int rc = getsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));

5.3设置属性

int setsockopt (SOCKET s, int level, int optname, const char FAR * optval, int optlen)
s  为欲设置属性的套接字。
level 为套接字选项的级别,用法同上。
optname 为设置选项的名称
optval  为存放选项值的缓冲区指针。
optlen  为缓冲区的长度
用法:
int ttl=32; //设置 TTL 值
int rc = setsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));

6.Winsock的使用步骤

6.1启动
对 Winsock DLL 进行初始化,协商 Winsock 的版本支持并分配必要的资源。(服务器端和客户端)

int WSAStartup( WORD wVersionRequested,LPWSADATA lpWSAData )
wVersionRequested 为打算加载 Winsock 的版本,一般如下设置:
wVersionRequested=MAKEWORD(2.0)或者直接赋值:wVersionRequested=2
LPWSADATA 为初始化 Socket 后加载的版本的信息,定义如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * l  pVendorInfo;
} WSADATA, FAR * LPWSADATA;
如果加载成功后数据为:
wVersion = 2 表示加载版本为 2.0。
wHighVersion = 514 表示当前系统支持 socket 最高版本为 2.2。
szDescription = "WinSock 2.0"
szSystemStatus = "Running" 表示正在运行。
iMaxSockets = 0 表示同时打开的 socket 最大数,为 0 表示没有限制。
iMaxUdpDg = 0 表示同时打开的数据报最大数,为 0 表示没有限制。
lpVendorInfo 没有使用,为厂商指定信息预留。
该函数使用方法:
WORD wVersion = MAKEWORD(2,0);
WSADATA wsData;
int nResult = WSAStartup(wVersion, &wsData);
if(nResult != 0)
{
//错误处理
}

6.2创建

SOCKET socket( int af, int type, int protocol );
af  为网络地址类型,一般为 AF_INET,表示在 Internet 域中使用。
type 为套接字类型,前面已经介绍了。
protocol  为指定网络协议,一般为 IPPROTO_IP。
用法:
SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock==INVALID_SOCKET)
{
//错误处理
}

6.3绑定

int bind( SOCKET s, const struct sockaddr FAR * name, int namelen )
s  为已经创建的套接字。
name  为 socket 地址结构,为 sockaddr 结构,如前面讨论的,我们一般使用 sockaddr_in 结构,在使用再强制转换
为 sockaddr 结构。
namelen 为地址结构的长度。
用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port= htons(0); //保证字节顺序
addr. sin_addr.s_addr= inet_addr("192.1.8.84")
int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//错误处理
}

6.4监听(服务器端)

int listen(SOCKET s, int backlog )
s  为一个已绑定但未联接的套接字。
backlog  为指定正在等待联接的最大队列长度,这个参数非常重要,因为服务器一般可以提供多个连接。
用法:
int nResult=listen(s,5) //最多 5 个连接
if(nResult==SOCKET_ERROR)
{
//错误处理
}

6.5等待连接(服务器端)

SOCKET accept( SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen )
s  为处于监听模式的套接字。
sockaddr 为接收成功后返回客户端的网络地址。
addrlen  为网络地址的长度。
用法:
sockaddr_in addr;
SOCKET s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
if(s==INVALID_SOCKET)
{
//错误处理
}

6.6连接(客户端)

int connect (SOCKET s, const struct sockaddr FAR * name, int namelen )
s  为欲连结的已创建的套接字。
name  为欲连结的 socket 地址。
namelen 为 socket 地址的结构的长度。
用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port=htons(0); //保证字节顺序
addr. sin_addr.s_addr= htonl(INADDR_ANY) //保证字节顺序
int nResult=connect(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//错误处理
}

6.7发送数据(服务器端和客户端)

int send(SOCKET s, const char FAR * buf, int len, int flags )
s  为服务器端监听的套接字。
buf 为欲发送数据缓冲区的指针。
len  为发送数据缓冲区的长度。
flags 为数据发送标记。
返回值  为发送数据的字符数。
flag 取值必须为 0 或者如下定义的组合:0 表示没有特殊行为。
#define MSG_OOB 0x1 /* process out-of-band data */
#define MSG_PEEK 0x2 /* peek at incoming message */
#define MSG_DONTROUTE 0x4 /* send without using routing tables */
MSG_OOB 表示数据应该带外发送,所谓带外数据就是 TCP 紧急数据。
MSG_PEEK 表示使有用的数据复制到缓冲区内,但并不从系统缓冲区内删除。
MSG_DONTROUTE 表示不要将包路由出去。
用法:
char buf[]="xiaojin";
int nResult=send(s,buf,strlen(buf));
if(nResult==SOCKET_ERROR)
{
//错误处理
}

6.8接受数据(客户端)

int recv( SOCKET s, char FAR * buf, int len, int flags )
s  为准备接收数据的套接字。
buf 为准备接收数据的缓冲区。
len  为准备接收数据缓冲区的大小。
flags 为数据接收标记。
返回值  为接收的数据的字符数。
基于 Windows 的 的 Socket  模型 
11 
用法:
char mess[1000];
int nResult =recv(s,mess,1000,0);
if(nResult==SOCKET_ERROR)
{
//错误处理
}

6.9中断连接(服务器端和客户端)

int shutdown(SOCKET s, int how)
s  为欲中断连接的套接字。
how 为描述禁止哪些操作,取值为:SD_RECEIVE、SD_SEND、SD_BOTH。
#define SD_RECEIVE 0x00
#define SD_SEND 0x01
#define SD_BOTH 0x02
用法:
int nResult = shutdown(s,SD_BOTH);
if(nResult == SOCKET_ERROR)
{
//错误处理
}

6.10关闭(服务器端和客户端)

int closesocket( SOCKET s )
s  为欲关闭的套接字。
用法:
int nResult = closesocket(s);
if(nResult == SOCKET_ERROR)
{
//错误处理
}
发布了53 篇原创文章 · 获赞 19 · 访问量 2375

猜你喜欢

转载自blog.csdn.net/m0_37757533/article/details/105565721