【转】Windows socket基础
转自:http://blog.csdn.net/ithzhang/article/details/8448655
Windows socket 基础
Windows socket是一套在Windows操作系统下的网络编程接口。它不是一种网络协议,而是一个开放的、支持多个协议的Windows下的网络编程接口
。
Windows socket是以Unix socket为基础,因此Windows socket中的许多函数名与Unix都是一样的。除此之外它还允许开发人员充分利用Windows的消息驱动机制进行程序设计开发。
套接字是应用层到运输层的接口。套接字用以表示一条连接的两端。每一个端点由ip和端口组成。因此套接字是由两端点的ip和端口组成。
端口是运输层的概念,每个端口对应一个进程。因此一条连接表示一个进程与另一个进程建立联系。
应用程序可以使用两种套接字。流套接字和数据包套接字。分别对应TCP和UDP。
TCP提供面向连接的可靠的、无重复、有序的数据流服务。而UDP提供面向数据包的,不保证数据是可靠的、有序的和无重复的。
在Windows环境下,使用Windows socket api进行网络程序开发时,需要调用Windows操作系统的Windows socket动态库。在应用程序中需要包含Windows sockets头文件。windows sockets 2.2版本需要包含WINSOCK2.h头文件(不区分大小写)。同时还需要添加动态库。一种是在头文件中添加。如:
#pragma comment(lib,"WS2_32.lib")
另一种是在vc中添加。可以选择project -》settting,在link标签下添加wsock32.lib字符串。
Windows sockets中定义的套接字类型SOCKET来表示套接字:
- typedef unsigned int u_int;
- typedef u_int SOCKET;
其实所谓的SOCKET的类型只不过是unsigned int的别名罢了。INVALID_SOCKET表示一个无效的套接字,除此之外的0--INVALID_SOCKET-1都表示一个有效的套接字。因此在创建套接字后,都需要与INVALID_SOCKET比较,看创建的套接字是否有效。
- SOCKET s=socket(...);
- if(INVALID_SOCKET==s)
- {
- //创建失败。
- }
Windows SOCKET可以支持多种不同的网络协议,并且提供与协议无关的编程接口。因此开发人员就可以相同的格式开发使用任一协议的网络应用程序,而不去关心各种协议的不同。
每种协议都有一套不同的IP定址方案(即表示主机地址的方式)。TCP协议和UDP协议通过IP协议传输数据。
而Windows SOCKET通过AF_INET地址家族为IP协议定址。
- #define AF_INET 2
网络中每台主机都有一个IP地址,用32位数字来表示。TCP和UDP必须指定端口号。在Windows SOCKET中sockaddr_in 结构被用来指定IP和端口号。
- struct sockaddr_in
- {
- short sin_family;
- u_short sin_port;
- struct in_addr sin_addr;
- char sin_zero[8];
- };
sin_family表示地址家族。使用TCP/IP协议的应用程序必须为aF_INET,来告诉系统使用IP地址家族 。
sin_port指定服务的端口号。1024--49151范围内的数据被作为服务端口号,可以由用户自定义。 sin_zero字段作为填充字段。以便使得该结构与SOCKADDR结构长度相同。
in_addr的定义如下:
- struct in_addr {
- union {
- struct{
- u_char s_b1,s_b2,s_b3,s_b4;
- }S_un_b;
- struct {
- u_short s_w1,s_w2;
- } S_un_w;
- u_long S_addr;
- } S_un;
- }
很显然它是一个存储ip地址的联合体,有三种表达方式:
第一种用四个字节来表示IP地址的四个数字;
第二种用两个双字节来表示IP地址;
第三种用一个长整型来表示IP地址。
给in_addr赋值的一种最简单方法是使用inet_addr函数,它可以把一个代表IP地址的字符串赋值转换为in_addr类型,如addrto.sin_addr.s_addr=inet_addr("192.168.0.2");
其反函数是inet_ntoa,可以把一个in_addr类型转换为一个字符串。
sockaddr类型sockaddr类型是用来表示socket地址的类型,同上面的sockaddr_in类型相比,sockaddr的适用范围更广,因为sockaddr_in只适用于TCP/IP地址。
sockaddr的定义如下:
- struct sockaddr {
- u_short sa_family;
- char sa_data[14];
- };
可知sockaddr有16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockaddr的。事实上也往往使用这种方法。
不同cpu处理多字节时处理方式不同。Intel x86cpu对多字节的处理方式为高对高低对低。但是在网络上采用的是高对低,低对高的方式。因此也就存在所谓主机字节序和网络字节序的处理问题。
Htonl和htons函数实现主机字节顺序和网络字节序的转换功能。H代表host,主机。N代表net。L代表long。S代表short。不要使用htonl转换short哦,楼主就有一次犯了这个错误,找了好久才找到原因。
当然也有从网络字节序到主机字节序的转换函数:ntohl和ntohs。
除了支持TCP/IP协议之外,Windows SOCKET还支持IPX/SPX、ATM和红外线通信协议等等,它们都有自己的定址方法,感兴趣的同学可以参考其他资料。
基本的套接字编程。
在使用套接字进行编程之前,无论是服务器还是客户端都必须加载Windows SOCKET动态库。函数WSAStartup就实现了此功能。它是套接字应用程序必须调用的第一个函数。
- int WSAStartup(
- WORD wVersionRequested,
- LPWSADATA lpwsadata);
第一个参数指定准备加载的Windows SOCKET动态库的版本。一般使用宏MAKEWORD构造。如MAKEWORD(2,2)表示加载2.2版本。
WSADATA会返回被加载动态链接库的基本信息。如是否加载成功,以及当前实际使用的版本。具体结构不再介绍。
初始化socket之后,需要创建套接字。socket函数和WSASocket函数可以实现此功能。
- SOCKET socket(
- int af,
- int type,
- int protocao);
af表示使用协议的地址家族。创建TCP或UDP的套接字是使用AF_INET。
type表示套接字的类型。有SOCK_STREAM、SOCK_DGRAM和SOCK_RAM三种类型。分别表示流、数据包、原始套接字。
protocol,指示使用的协议。对于SOCK_STREAM套接字类型,该字段可以为IPPROTO_TCP或0。对于SOCK_DGRAM套接字类型,该字段为IPPROTO_UDP或0。
当函数创建成功时,返回一个新建的套接字句柄。否则将返回INVALID_SOCKET。如
- SOCKET s=socket(AF_INET,SOCK_STREAM,0);
- if(INVALID_SOCKET)
- {
- //失败。
- }
bind函数
在创建套接字之后就需要调用bind函数将其绑定到一个已知的地址上。
- int bind(SOCKET s,
- const struct sockaddr*name,
- int namlen);
s为要绑定的套接字,name为要绑定的地址。namelen为sockaddr长度。
函数调用成功将返回0,否则返回值为SOCKET_ERROR。如果程序不关心分配给它的地址,可使用INADDR_ANY或将端口号设为0。端口号为0时,Windows SOCKET将给应用程序分配一个值在1024-5000之间唯一的端口号。
示例:
- struct sockaddr_in addr;
- int nServerPort=5500;
- int nErrorCode;
- addr.family=AF_INET;
- addr.port=htons(nServerPort);
- addr.sin_addr.S_addr=htonl(INADDR_ANY);
- SOCKET s=socket(AF_INET,SOCK_STREAM,0);
- if(s==INVALID_SOCKET)
- {
- //失败。
- }
- nErrorCode=bind(s,(SOCKADDR*)&addr,sizeof(addr));
- if(nErrorCode==SOCKET_ERROR)
- {
- //失败。
- }
listen函数将套接字设定为监听模式。
- int listen (
- SOCKET s,
- int backlog);
s为要设置监听模式的套接字。
backlog指定等待连接的最大队列长度。
当函数成功时返回0,否则返回SOCKET_ERROR。
假如backlog为3,则说明最大等待连接的最大值为3.如果有四个客户端同时向服务器发起请求,那么第四个连接将会发生WSAEWOULDBLOCK错误。当服务器接受了一个请求,就将该请求从请求队列中删去。
- int ret=listen(s,3);
- if(ret==SOCKET_ERROR)
- {
- //失败。
- }
accept函数接受客户端的一个连接请求。
- SOCKET accept{
- SOCKET s,
- struct sockaddr*addr,
- int *addrlen);
s为监听套接字。
addr返回客户端地址。
addrlen返回addr的长度。
函数执行成功时:
1:主机接受了等待队列的第一个请求。
2:addr结构返回客户端地址。
3:返回一个新的套接字句柄。服务器可以使用该套接字与客户端进行通信。而监听套接字仍用于接受客户端连接。
- SOCKET sListen;//监听套接字。
- SOCKET sAccept;//接受套接字。
- sockaddr_in addrClient;//客户端地址。
- int addrClientLen=sizeof(addrClient);
- sAccept=accept(sListen,(SOCKADDR*)&addrClient,&addrClientLent);
- if(INVALID_SOCKET==sAccept)
- {
- //失败。
- }
recv()函数
recv()和WSARecv()函数用于接收数据。
- int recv(
- SOCKET s,
- char *buf,
- int len,
- int flags);
s为接收数据套接字。
buff接受缓冲区。
len缓冲区长度。
flags:该参数影响函数的行为。它可以是0,MEG_PEEK和MSG_OOB。0表示无特殊行为。MSG_PEEK会使有用的数据被复制到buff中,但没有从系统缓冲区内将这些数据删除。MSG_OOB表示处理外带数据。
recv函数返回接收的字节数。当函数执行失败时返回SOCKET_ERROR。
- SOCKET s;
- char buff[128];
- nReadlen=recv(s,buff,128,0);
- if(SOCKET_ERROR==nReadlen)
- {
- }
send函数
send()和WSASend()用于发送数据。
- int send(
- SOCKET s,
- const char *buff,
- int len,
- int flags);
s为发送套接字。
buff为发送缓冲区。
len发送数据长度。
flags可以是0或MSG_DONTROUTE或MSG_OOB。0表示无特殊行为。MSG_DONTROUTEyaoqiu传输层不要将此数据路由出去。MSG_OOB表示该数据应该被外带发送。
函数成功时返回实际发送的字节数。失败返回SOCKET_ERROR。
- SOCKET s;
- char buff[128];
- int ret;
- strcpy(buff,”sendData”);
- ret=send(s,strlen(buff),0);
- if(SOCKET_ERROR==ret)
- {
- }
closesocket()函数
closesocket()函数用以关闭套接字。释放套接字所占资源。
- int closesocket(
- SOCKET s);
调用过closesocket函数的套接字继续使用时会返回WSAENOTSOCK错误。
shutdown()函数。
Shutdown()函数用于通知对方不再发送数据或者不再接收数据或者既不发送也不接收数据。
- int shutdown(
- SOCKET s,
- int how);
how参数可以是:
SD_RECEIVE表示不再接收数据,不允许再调用接收数据函数。
SD_SEND表示不再发送数据。
SD_BOTH表示既不发送也不接收数据。
connect函数实现连接服务器的功能。
- int connect(
- SOCKET s,
- const struct sockaddr*name,
- int namelen);
s为套接字。
name为服务器地址。
namelen为sockaddr结构长度。
函数执行成功返回0,否则返回SOCKET_ERROR。
- SOCKET s;
- ULONG ulServIp;
- USHORT ServPort;
- int ret;
- SOCKADDR_IN servAddr;
- servAddr.sin_family=AF_INET;
- servAddr.sin_addr.S_addr=htonl(ulServIp);
- servAddr.sin_port=htons(ServPort);
- int len=sizeof(servaddr);
- ret=connect(s,(SOCKADDR*)&servAddr,sizeof(servAddr));
- if(ret==SOCKET_ERROR)
- {
- }
创建套接字后,可以对它的各种属性进行设置。可以调用getsockopt()函数来返回套接字选项信息。setsockopt()设置套接字选项。
getsockopt()函数
它用于获取套接字选项信息:
- int getsockopt(
- SOCKET s,
- int level,
- int optname,
- char *optval,
- int optlen);
s为要取得选项的套接字。
level为选项级别,有SOL_SOCKET和IPPROTO_TCP两个级别。
optname套接字选项名称。
optval该参数返回套接字选项名称对应的值。
optlen为缓冲区optval大小。
函数成功,返回值为0。否则返回SOCKET_ERROR。
- int Bufflen;
- int noptlen=sizeof(nBufflen);
- int ret=getsockopt(s,SOL_SOCKET,SO_RCVBUF,(char*)&BuffLen,&noptlen);
- if(ret==SOCKET_ERROR)
- {
- }
setsockopt函数。
它可以设置套接字选项。若不能正确设置socket属性,则数据的发送和接收会失败
- int setsockopt(
- SOCKET s,
- int level,
- int optname,
- char *optval,
- int optlen);
s为要取得选项的套接字。
level为选项级别,有SOL_SOCKET和IPPROTO_TCP两个级别。
optname套接字选项名称。
optval参数设置套接字选项的值。
optlen为optval大小。
下列代码首先调用getsockopt函数获得默认接收缓冲区的大小,然后调用setsockopt将接收缓冲区大小设置为原来的10倍。再次调用getsockopt来检查是否设置成功。
- int opt;
- int noptlen=sizeof(opt);
- int ret=getsockopt(s,SOL_SOCKET,SO_RECVBUFF,(char*)&opt,noptlen);
- if(ret==SOCKET_ERROR)
- {
- }
- opt*=10;
- ret=setsockopt(s,SOL_SOCKET,SO_RECVBUFF,(char*)&opt,noptlen);
- if(ret==SOCKET_ERROR)
- {
- }
- int newopt;
- getsockopt(s,SOL_SOCKET,SO_RECVBUFF,(char*)&newopt,&noptlen);
- if(newopt!=opt)
- {
- //设置失败。
- }
当设置SOL_SOCKET选项级别时,调用setsockopt函数和getsockopt函数所设置或获取的信息为套接字本身的特征,这些信息与基层协议无关。
SOL_SOCKET级别包括一下类型:
SO_ACCEPTCONN bool类型。如果为真,表明套接字处于监听模式。
SO_BROADCAST bool类型,如果为真,表明套接字已设置成广播消息发送。
SO_DEBUG bool类型。如果为真,则允许输出调试信息。
SO_DONTLINGER bool类型。如果为真,则禁止SO_LINGER。
SO_DONTROUTE bool类型。如果为真,则不会做出路由选择。
SO_ERROR int 类型。返回和重设以具体套接字为基础的错误代码。
SO_KEEKPALIVE bool类型。如果为真,则套接字在会话过程中会发送保持活动消息。
SO_LINGER struct linger*类型,设置或获取当前的拖延值。
SO_OOBINLINE bool如果为真,则带外数据会在普通数据流中返回。
SO_RECVBUF int类型。设置或获取接收缓冲区长度。
SO_REUSEADDR bool类型。如果为真,套接字可以与一个正在被其他套接字使用的地址绑定。
SO_SNDBUF bool类型。设置或获取发送缓冲区大小。
SO_TYPE int类型。返回套接字类型,如SOCK_DGREAM,SOCK_STREAM。
SO_SNDTIMEO int类型。设置或获取套接字在发送数据的超时时间。
SO_RECVTIMEO int类型,设置或获取套接字在接收数据的超时时间。
WSAGetLastError函数
该函数用来在Socket相关API失败后读取错误码,根据这些错误码可以对照查出错误原因。
GetComputerName()用来获取计算机名称:
- BOOL GetComputerName( LPTSTR lpBuffer, LPDWORD lpnSize);
- plBuffer是用来存储返回的名称的缓冲区。
lpBuffer为缓冲区。
lpnSize为缓冲区大小。同时它也返回计算机名称的长度。
此函数不属于winsock库函数。使用之前不需要初始化库。
使用方法为:
- <span style="font-size:18px;"> TCHAR szHostName[20];
- DWORD dwSize = 20;
- GetComputerName( szHostName, &dwSize );
- </span>
gethostname函数:
此函数为WinSock库函数,使用之前需要初始化WinSock库。
- int gethostname(char *name, int namelen);
name为存储主机名缓冲区。
namelen为缓冲区长度。
以上参考自《Windows sockets网络开发--基于Visual C++实现》如有纰漏,请不吝赐教!!!
写了个例子:http://files.cnblogs.com/cappuccino/Socket.rar
【转】Windows socket基础的更多相关文章
- 【Windows socket+IP+UDP+TCP】网络基础
Windows Socket+网络 Winsock是 Windows下套接字标准. Winsock 编程分为UDP[Windows socket + UDP],TCP[Wi ...
- Windows Server基础架构云参考架构:硬件之上的设计
作者 王枫 发布于2014年1月27日 综述 毫无疑问,移动互联网.社交网络.大数据和云计算已经成为IT发展的四个大的趋势.其中云计算又为前三个提供了一个理想的平台.今天不仅互联网公司,很多传统行业的 ...
- windows socket 网络编程
样例代码就在我的博客中,包含六个UDP和TCP发送接受的cpp文件,一个基于MFC的局域网聊天小工具project,和此小工具的全部执行时库.资源和执行程序.代码的压缩包位置是http://www.b ...
- windows socket函数详解
windows socket函数详解 近期一直用第三方库写网络编程,反倒是遗忘了网络编程最底层的知识.因而产生了整理Winsock函数库的想法.以下知识点均来源于MSDN,本人只做翻译工作.虽然很多前 ...
- C#网络程序设计(2)Socket基础编程
本节介绍如何使用基础Socket实现TCP通信. (1)Socket详细介绍: Socket的英文原义是"孔"或"插座".通常称作"套 ...
- windows socket网络编程资料汇集
windows socket网络基础详解(socket的流程介绍的很详细)http://blog.csdn.net/ithzhang/article/details/8448655 Windows S ...
- 高性能 Windows Socket 组件 HP-Socket v3.0.2 正式发布
HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和客户端组件(Event Select 模型),广泛适用于 Windows 平台的 TCP ...
- 高性能 Windows Socket 组件 HP-Socket v3.0.1 正式发布
HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和客户端组件(Event Select 模型),广泛适用于 Windows 平台的 TCP ...
- 高性能 Windows Socket 组件 HP-Socket v2.3.1-beta-2 发布
HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和客户端组件(Event Select 模型),广泛适用于 Windows 平台的 TCP ...
随机推荐
- iOS:App 内部更改使用语言/ 重定义系统的宏
代码部分 #undef NSLocalizedString #define NSLocalizedString(key, comment) \ NSLocalizedStringFromTable(k ...
- webService(SOAP)性能测试脚本
本文以天气预报的webService为基础进行学习 webService地址:http://www.webxml.com.cn/WebServices/WeatherWebService.asmx ...
- BarTender 2016表单中的“秤显示”控件
BarTender 2016中的表单是一个非常实用的工具,它可以实现数据输出提示,查询提示和同一表单的记录选择.这些都离开可供添加的控件,“秤显示”控件也是我们打印尝尝需要涉及的,今天我们就来看看什么 ...
- Android开发学习笔记-GridView的动态显示
1.添加GridItem布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout x ...
- opengl库区分:glut、freeglut、glfw、glew、gl3w、glad
//oepngl库 opengl原生库 gl* 随opengl一起发布 opengl实用库 glu* 随opengl一起发布 opengl实用工具库glut glut* 需要下载配置安装(太老了!) ...
- Java正则表达式的使用和详解(下)
1.常用正则表达式 规则 正则表达式语法 一个或多个汉字 ^[\u0391-\uFFE5]+$ 邮政编码 ^[1-9]\d{5}$ QQ号码 ^[1-9]\d{4,10}$ 邮箱 ^[a-zA ...
- winform 打开一个窗体,关闭一个窗体
例如 我要打开一个窗体b,关闭一个窗体a a中的代码添加: private void pictureBox5_Click(object sender, EventArgs e) { W_MainFo ...
- Shiro集成Spring
本篇博客主要讲述的是两者的集成.不涉及到各自的详细细节和功能. 因为官方给出的文档不够具体,对新手而言通过官方文档还不可以非常快的搭建出SpringShiro的webproject.本博客将通过实际的 ...
- Python中的类(中)
上一篇介绍了Python中类相关的一些基本点,本文看看Python中类的继承和__slots__属性. 继承 在Python中,同时支持单继承与多继承,一般语法如下: class SubClassNa ...
- [Ubuntu] APT - Advanced Packaging Tool 简明指南
Advanced Packaging Tool,一般简称为apt,是Debian GNU/Linux distribution及其变体版本中与核心库一道处理软件的安装和卸载. Ubuntu是Debia ...