windows sock 网络编程基础知识--基本TCP套接字编程

来源:互联网 发布:求实软件电话 编辑:程序博客网 时间:2024/04/29 16:01

#include "winsock2.h"//头文件
#pragma comment(lib, "wsock32.lib")//导入wsock32.lib库文件

TCP和UDP的区别
简单的说,就是有建立连接(TCP)和无建立连接(UDP)。

typedef unsigned int u_int; //u_int 为无符号整形
typedef u_int SOCKET; //SOCKET为无符号整形,称为套接字

在windows Sockets中除了INVALID_SOCKET不是一个有效的套接字外,在0-INVALID_SOCKET-1之间的数值

都是有效的套接字。

判断是否成功的创建了套接字的方法是将socket()函数的返回值与INVALID_SOCKET进行比较
eg:
 SOCKET s = socket(...);
 if (INVALID_SOCKET == s)
 {
  //失败
 }

INVALID_SOCKET 的声明如下:
#define INVALID_SOCKET (SOCKET)(~0)   //~为按位取反运算符,在32位中INVALID_SOCKET为-1
在32位系统中对于0全部取反就是 1111 1111 1111 1111  等于-1。 简单记忆:0+1取反等于-1

常量SOCKET_ERROR是被用来检查Windows API调用失败的。
SOCKET_ERROR的声明如下:
#define SOCKET_ERROR            (-1)


Windows Sockets API 提供了shutdown()和WSASendDisconnect()函数实现关闭连接的功能。
closesocket()函数实现关闭套接字的功能,但同时也隐含执行shutdown()函数的功能。

Windows Sockets 通过AF_INET地址家族为IP通信定址。其中“A”代表address,“F”代表family。
AF_INET的声明如下:
#define AF_INET    2;

Windows Sockets API提供了SOCKADDR_IN结构来指定IP地址和端口号
该结构声明如下:
struct sockaddr_in{
 short    sin_family;
 u_short  sin_port;
 struct   in_addr sin_addr;
 char     sin_zero[8];
};

sin_famliy: 地址家族
sin_port:   服务端口号
sin_addr:   in_add类型的IP地址
sin_zero:   填充该结构的大小,使之与SOCKADD结构大小相同

sin_family字段必须为AF_INET,以告知Windows Sockets应用程序使用IP地址家族。
sin_port字段指定了服务的端口号。1024-49151
sin_addr字段用于把一个IP地址保存为一个4字节的数值
sin_zero字段充当填充项的职责,以便使得该结构与SOCKADDR结构长度相同

字节顺序问题
在计算机中把IP地址和端口号指定为多字节数字时,这个数就按照“主机字节”(hose-byte)顺序表示

。但是在网络上指定IP地址和端口号,这个数必须按照“大头”形式来表示,也就是按照从最有意义的字

节到最无意义的字节来表示数据,这里称为“网络字节”顺序(network-byte)

htonl()函数和htons()函数实现从主机字节顺序转换为网络字节顺序的功能。其中“h”代表机“host”

;“n”代表“net”;“l”代表“long”;“s”代表“short”;“to”表示转换的含义。

htonl()函数和htons()函数声明如下。
u_long  htohl(u_long  hostlong);
u_short htohs(u_short hostshort);


ntohl()函数和ntohs()函数实现从网络字节顺序转换为主机字节顺序的功能。

ntohl()函数和ntohs()函数声明如下。
u_long ntohl(u_long  netlong);
ushort ntohs(u_short netshort);

客户端连接服务器时,必须先知道服务器的名称。
在TCP/IP中,服务器的名称 = 服务器的IP地址和端口号。

服务端--创建套接字--绑定--监听--接受连接--发送数据--关闭
bind()函数实现将服务器绑定到一个已知的名字上的功能。
接下来需要将服务器套接字设置为监听状态,以便监听客户端的连接,这是通过调用listen()函数完成的
在监听状态下,如果客户端向服务器发起连接请求,服务器通过调用accetp()函数来接受该连接请求

服务端与客户端完成连接后,借可以进行数据通信了。

客户端--创建套接字--连接--发送数据--关闭
调用connect()函数向服务器发出连接请求。


WSAStartup()函数
开发Windows Sockets应用程序时,必须首先加载Windows Sockets动态库(DLL)。WSAStartup()函数实

现此项功能。该函数是套接字应用程序必须调用的第一个函数。

该函数的声明如下:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

wVersionRequested:指定准备加载Windows Sockets动态库的版本。
lpWSAData:指向LPWSADATA结构的指针,该参数返回被加载动态库的有关信息。


WSADATA结构声明如下:
#define WSADESCRIPTION_LEN 256
#define WSASYS_STATUS_LEN 128
typedef struct WSAData
{
 WORD           wVersion;
 WORD           wHigtVersion;
 char           szDescription{WSADESCRIPTION_LEN+1};
 char           szSystemStatus{WSASYS_STATUS_LEN+1};
 unsigned short iMaxSockets;
 unsigned short iMaxUdpDg;
 char   FAR*    lpVendorInfo;
}WSADATA, FAR * LPWSADATA;

wVersion:期望调用者使用的Windows Sockets版本
wHighVersion:DLL支持的最高版本
szDescription:DLL的描述信息
szSystemStatus:DLL的状态信息
iMaxSockets:一个进程可以打开的套接字最多数量(Windows Sockets2.0及以后版本中忽略此值)
iMaxUdpDg:一个进程发送或接收的最大的数据包的长度(Windows Sockets2.0及以后版本中忽略此值)
lpVendorInfo:厂商专有信息(Windows Sockets2.0及以后版本中忽略此值)

socket()函数
初始化Windows Sockets DLL之后,创建套接字。socket()函数和WSASocket()函数将实现此功能。

socket()函数声明如下:

SOCKET socket(int af, int type, int protocol);
af:协议的地址家族。创建TCP或者UDP套接字时,该参数为AF_INET。
type:协议的套接字类型。有SOCK_STREAM,SOCK_DGRAM和SOCK_RAM 3种类型
protocol:协议。指定的地址家族和套接字类型有多个数目时,使用该字段来限定一个特殊的传输。对于

SOCK_STREAM套接字类型,该字段为IPPROTO_TCP或者为0;对于SOCK_DGRAM套接字类型,该字段为

IPPROTO_UDP或者为0.

当该函数调用成功后,返回一个新建的套接字句柄,调用失败则返回INVALID_SOCKET.
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(INVALID_SOCKET == s)
{
//调用失败
}

bind()函数
bind()函数将套接字绑定到一个已知的地址。
该函数声明如下:
int bind(SOCKET s, const struct sockaddr FAR * name, int namelen);
s:套接字
name:地址
namelen:sockaddr结构长度
如果该函数调用成功,则返回值为0,失败则返回值为SOCKET_ERROR
如果地址字段为INADDR_ANY,则可使用任意网络接口。
如果端口号设置为0,则Windows Sockets将给应用程序分配一个值在1024-5000之间的唯一端口

eg:
SOCKET             s;//套接字
struct sockaddr_in servAddr;//服务器套接字地址
int                nServPort = 5500;//服务器端口
int                nErrCode;//错误代码

//定义服务器地址
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_addr = htonl(INADDR_ANY);
servAddr.sin_family = htons(nServPort);

//绑定套接字
nErrcode = bind(s, (SOCKADDR*) &servAddr, sizeof(servAddr));
if(SOCKET_ERROR == nErrCode)
{
//绑定套接字失败
}

listen()函数
listen()函数将套接字设置为监听模式。
listen()函数声明如下:
int listen(SOCKET s, int backlog);
s:套接字
backlog:指定等待连接的最大队列长度
该函数调用成功时,返回值为0,失败则返回SOCKET_ERROR.

accept()函数
accept()函数实现接受一个连接请求的功能
accept()声明如下:
SOCKET accept(SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen);
s:监听套接字。
addr:该参数返回请求连接主机的地址
addrlen:该参数返回SOCKADDR_IN结构的长度。
当该函数成功返回时:
主机接受了等待队列中的第一个请求
addr结构返回发起请求的客户端的地址,addrlen参数指出了地址结构的长度。
返回一个新的套接字句柄。服务器使用该套接字与客户端进行数据传输。而监听套接字仍然用于接受客户

端的连接
调用失败时返回INVALID_SOCKET错误

SOCKET sListen;//监听套接字
SOCKET sAccept;//接受套接字
sockaddr_in addrClient;//客户端地址

int addrClientlen = sizeof(addrClient);//长度

//接受客户请求
sAccept = accept(sListen, (SOCKADDR*) &addrClient, &addrClientlen);
if(INVALID_SOCKET == sAccept)
{
//失败处理
}

recv()函数
recv()和WSARecv()函数用于接受数据
recv()函数声明如下:
int recv(SOCKET s, char FAR * buf, int len, int flags);
s:套接字
buf:接受数据缓冲区
len:缓冲区长度
flags:该参数影响该函数的行为。该参数可以是0,MSG_PEEK和MSG_OOB.0表示无特殊行为;MSG_PEEK会

使有用的数据被复制到接收缓冲区内,但没有从系统缓冲区中将其删除,MSG——OOB表示处理带外数据。

该函数成功返回时返回值为接收的字节数。失败时则返回SOCKET_ERROR.

SOCKET s;//套接字
char   buf[128];//接收数据缓冲区
int    nReadlen;//接收字节数

//接收数据
nReadlen = recv(s, buf, 128, 0);
if(SOCKET_ERROR == nReadlen)
{
//失败处理
}

send()函数
send()和WSASend()函数用于发送数据
send()函数声明如下:
int send(SOCKET s, const char FAR * buf, int len, int flags);
s:套接字
buf:发送数据缓冲区
len:发送数据长度
flags:该参数影响该函数的行为,该参数可以为0,MSG_DONTROUTE或者MSG_OOB。0表示无函数无特殊行

为,MSG_DONTROUTE标志要求传输层不要将数据路由出去,MSG_OOB标志表示该数据应该被带外发送。

该函数成功返回时,返回值为实际发送的字节数。失败时返回SOCKET_ERROR。

SOCKET   s;//套接字
char     buf[128];//接受数据缓冲区
int      retVal;//返回值

strcpy(buf, "send data");

//发送数据
retVal = send(s, buf, strlen(buf),0);
if(SOCKET_ERROR == retVal)
{
//失败处理
}

closesocket()函数
closesocket()函数关闭套接字,释放所占资源。
该函数输声明如下:
int closesocket(SOCKET s);

当调用该函数释放套接字后,如果在使用该套接字执行函数调用,则会失败并返回WSAENOTSOCK错误。

shutdown()函数
shutdown()函数用于通知对方不再发送数据,或者不再接收数据,或者既不发送也不接收数据
该函数声明如下:

int shutdown(SOCKET s, int how);
s:套接字
how:如果该参数为SD_RECEIVE,则表示不允许再调用接收数据函数;如果该参数为SE_SEND,则表示不允

许再调用发送数据函数;如果该参数为SD_BOTH,则表示既不允许调用发送数据函数也不允许调用接收数

据函数

connect()函数
connect()函数实现连接服务器功能。
该函数声明如下:

int connect(SOCKET s, const struct cockaddr FAR * name, int namelen);
s:套接字
name:服务器地址
namelen:sockaddr结构的长度

当该函数调用成功时,函数返回0,调用失败时返回SOCKET_ERROR.

SOCKET  s;//套接字
u_long  ulServIP;//服务器IP
u_short usServPort;//服务器端口
int     setVal;//返回值

//服务器地址
SOCKADDR_IN servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = htonl(ulServIP);
servAddr.sin_Port = htos(usServPort);
int nServLen = sizeof(servAddr);

//连接服务器
retVal = connect(s, (LPSOCKADDR)&servAddr, sizeof(servAddr));
if(SOCKET_ERROR == retVal)
{
//失败处理
}


开发过程:
Server:
WSAStartup()--socket()--bind()--listen()--accept()--recv()--closesocket--WSACleanup()
Client:
WSAStartup()--socket()--connect()--send()--closesocket()--WSACleanup()