WinSocket

网络编程是个很大的课题,这里只限Windows Socket编程。转一篇别人的文章,仅供参考。

 

一, 什么是Socket

接触网络编程当然要了解Socket,Socket(套接字)是一种网络编程接口,Socket提供了很多灵活的函数,帮助程序员写出高效的网络应用。Socket分为BSD UNIX和windows两个版本。

在win32平台上的Winsock编程都要经过下列基本步骤:

定义变量——获得Winsock版本——加载Winsock库——初始化——创建套接字——设置套接字——关闭套接字——卸载Winsock库

使用winsock2 API编程,必须包含头文件winsock2.h (链接环境WS2_32.LIB),头文件winsock.h(WSOCK32.LIB)是为了兼容winsock1程序时使用的,另外mswsock.h(MSWSOCK.DLL)是微软的扩展类,用于开发高性能的winsock程序。

准备好后,你就可以着手建立你的第一个网络程序了。

二, Socket编程的基本过程

Socket通信分为面向连接的通信(TCP)和面向无连接的通信(UDP).


1,Winsock初始化和结束

每一个winsock应用程序必须首先加载相应的winsock dll版本。方法是调用:

int WSAStartup(

WORD wVersionRequested, 库版本,高字节副版本,低字节主版本

LPWSADATA lpWSAData 结构指针,函数自动填充该结构。

); 函数调用成功返回0

可以用宏MAKEWORD(x, y)用来指定第一个参数的值

2,建立套接字

套接字是传输提供者的一个句柄。

SOCKET socket (

int af,

int type,

int protocol IPPROTO_TCP, IPPROTO_UDP, 0(如果不想指定)

);

第一个参数指定通信协议的协议族,AF_INET(IPv4)或 AF_INET6(IPv6)(因为Socket是网络编程接口而不是一个协议,它使用流行的网络协议(TCP/IP,IPX)为应用程序提供的一个编程接口。)

第二个参数指定要创建的套接字的类型。SOCK_STREAM(TCP流套接字), SOCK_ DGRAM(UDP 数据包套接字),SOCK_RAW(原始套接字)

第三个参数指定应用程序所指定应用程序所使用的通信协议。

函数成功返回套接字描述符,失败返回INVALID_SOCKET

3,配置套接字

当创建一个套接字后,再进行网络通信之前,必须先配置Socket。面向连接的客户端Socket通过调用connect函数在Socket数据结构中保存地址和远端信息。无连接客户端,服务端以及面向连接Socket的服务端,通过调用bind函数来配置本地信息。

int bind(

SOCKET s, 创建的套接字

const struct sockaddr FAR* name, 指向地址缓冲区的指针

int namelen 地址缓冲区的大小

);

成功返回0,失败返回SOCKET_ERROR

当创建一个套接字后,套接字数据结构中有一个默认的IP地址和默认的端口号。一个服务程序必须调用bind函数来给其绑定一个IP地址和一个特定的端口号。

第二个参数指定一个sockaddr结构定义如下:

struct sockaddr {

u_short sa_family;

char sa_data[14];

};

我们通常使用另外一个等价的地址结构:


de>struct sockaddr_in { de>
de> short sin_family; de>
de> u_short sin_port; de>
de> struct in_addr sin_addr; de>
de> char sin_zero[8]; de>
de>}; de>
de>其中sin_family是通信协议族, de>
de>sin_port de> de>指明端口号, de>
de>sin_addr de> de>结构中有一个字段s_addr,表示IP地址,该字段是一个整数, de>
de>一般用函数inet_addr把点分字符串形式的IP地址转化成unsigned long型的整数值。 de>
de> de> de>如果指定为htonl(INADDR_ANY),那么无论哪个网段上的客户机都能与该服务器通信, de>
de>否则,只有与指定IP地址处于同一网段上的客户机能与该服务器通信。 de>
de>sin_zero[8] de> de>为填充,使两个结构大小相同。 de>
de> de>

de>有一些细节学要说明:de> 在计算机把IP地址和端口号指定成多字节时,这个数是按“主机字节”(host-byte)顺序表示的,不同的处理器对数的表示方法有“大头”(big-endian——最有意义的字节到最无意义的字节)和“小头”(little-endian)两种形式。但是如果在网络上指定IP地址和端口号时,必须按照big-endian 的形式,一般称之为“网络字节”(network-byte)顺序。

以下几个函数将主机字节顺序转换成网络字节顺序:

u_long htonl(u_long hostlong);

int WSAHtonl(

SOCKET s,

u_long hostlong,

u_long FAR * lpnetlong 通过指针返回网络字节顺序的4个字节的数

);

u_short htons(u_short hostshort);

int WSAHtons(

SOCKET s,

u_short hostshort,

u_short FAR * lpnetshort 通过指针返回网络字节顺序的2个字节的数

);

以下几个函数将网络字节顺序转换成主机字节顺序:

u_long ntohl(u_long netlong);

int WSANtohl(

SOCKET s,

u_long netlong,

u_long FAR * lphostlong

);

u_short ntohs(u_short netshort);

int WSANtohs(

SOCKET s,

u_short netshort,

u_short FAR * lphostshort

);


de> de>
de>

4,实现功能

(1) 服务器端: 需要对绑定的端口进行侦听,函数原型如下


de>int listen ( de>
de> SOCKET de> de> s de> de>, de> de> de>
de> int de> de> backlog de> de> de>
de>); de> de> de>

de>Backlogde>de>是客户连接请求队列的最大数量,而不是客户机连接的数量限制。de>

de>处于侦听的套接字将维护一个客户连接请求队列。de>

de> de>de>该函数执行成功返回0,失败返回de>SOCKET_ERROR

此外,需要从连接请求队列中取出最前面的一个客户请求,需要用到accept()函数:


de>SOCKET accept ( de>
de> SOCKET de> de> s de> de>, de> de> de>
de> struct sockaddr FAR* de> de> addr de> de>, de> de> de>
de> int FAR* de> de> addrlen de> de> de>
de>); de>
de>该函数创建一个新的套接字来与客户套接字建立通信,如果连接成功,就返回新建的套接字描述符, de>
de>以后与客户通信的就是该套接字,而侦听套接字则继续接受新的连接;如果失败就返回 de>INVALID_SOCKET
第一个参数是侦听套接字
第二个套接字用来返回新创建的套接字的地址结构
第三个套接字返回地址结构的长度
de> de>

(2) 客户端:

connect函数是客户机建立与远程服务器连接而使用的。


de>int connect ( de>
de> SOCKET de> de> s de> de>, de> de> de>
de> const struct sockaddr FAR* de> de> name de> de>, de> de> de>
de> int de> de> namelen de> de> de>
de>); de>
de> de>

5,数据传输

收发数据时网络编程的一切在这里我们只讨论同步函数send和recv,

不讨论异步函数WSASend和WSARecv。

数据发送要用到send函数,原型如下:


int send(
SOCKET s,
const char FAR * buf, 发送数据缓冲区的地址
int len, 要发送的字节数
int flags 一般为0, MSG_DONTROUTE, MSG_OOB(外带数据)
);
成功返回发送字节数,出错返回SOCKET_ERROR
注意send函数把数据从buf复制到socket发送缓冲区后就返回了,
但是这些数据并不时马上就发送到连接的另一端。

在已连接的套接字上接受数据,recv函数是最基本的方式。


int recv(
SOCKET s,
char FAR* buf,
int len, 准备接收的字节数或buf缓冲区长度
int flags 0, MSG_PEEK, MSG_OOB 其中MSG_PEEK表示将有用的数据复制到所提供的接收端缓冲区,
但是没有从系统缓冲区中将它删除
);

成功返回接收的数据的字节数量,失败返回SOCKET_ERROR,如果接受数据时网络中断返回0




6,关闭Socket


一旦完成任务,记得将套接字关闭以释放资源:
int closeSocket(SOCKET s)

三, Socket编程实例



讲了这么多其实还是看实例最为重要了,下面我提供了最简单的面向连接的客户端和服务端程序,
当服务端接受客户端的连接后,先是该客户机地IP地址,并向客户端发送一个回应消息,
最后关闭该连接套接字。这样的服务器当然没什么实际的用途。

设计一个基本的网络服务器有以下几个步骤:

1、初始化Windows Socket

2、创建一个监听的Socket

3、设置服务器地址信息,并将监听端口绑定到这个地址上

4、开始监听

5、接受客户端连接

6、和客户端通信

7、结束服务并清理Windows Socket和相关数据,或者返回第4步



#include <winsock2.h>

#include <stdio.h>

#define SERVPORT 5050

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

void main(void)

{

WSADATA wsaData;

SOCKET sListen; // 监听socket

SOCKET sClient; // 连接socket

SOCKADDR_IN serverAddr; // 本机地址信息

SOCKADDR_IN clientAddr; // 客户端地址信息

int clientAddrLen; // 地址结构的长度

int nResult;

// 初始化Windows Socket 2.2

WSAStartup(MAKEWORD(2,2), &wsaData);

// 创建一个新的Socket来响应客户端的连接请求

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// 填写服务器绑定的地址信息

// 端口为5150

// IP地址为INADDR_ANY,响应每个网络接口的客户机活动

// 注意使用htonl将IP地址转换为网络格式

serverAddr.sin_family = AF_INET;

serverAddr.sin_port = htons(SERVPORT);

serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

memset(&(serverAddr.sin_zero), 0, sizeof(serverAddr.sin_zero));

// 绑定监听端口

nResult = bind(sListen, (SOCKADDR *)&serverAddr, sizeof(SOCKADDR));

if (nResult == SOCKET_ERROR)

{

printf("bind failed!\n");

return;

}

// 开始监听,指定最大接受队列长度5,不是连接数的上限

listen(sListen, 5);

// 接受新的连接

while(1)

{

clientAddrLen = sizeof (SOCKADDR);

sClient = accept(sListen, (SOCKADDR *)&clientAddr, &clientAddrLen);

if(sClient == INVALID_SOCKET)

{

printf("Accept failed!");

}

else

{

printf("Accepted client: %s : %d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));

// 向客户端发送信息

nResult = send(sClient, "Connect success!", 16, 0);

if (nResult == SOCKET_ERROR)

{

printf("send failed!");

}

}

// 我们直接关闭连接,

closesocket(sClient);

}

// 并关闭监听Socket,然后退出应用程序

closesocket(sListen);

// 释放Windows Socket DLL的相关资源

WSACleanup();

}



客户机程序如下:

#include <winsock2.h>

#include <stdio.h>

#define SERVPORT 5050 // 端口为5150

#define MAXDATASIZE 100

#define SERVIP "127.0.0.1" // 服务器IP地址为"127.0.0.1",注意使用inet_addr将IP地址转换为网络格式

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

void main(int argc, char *argv[])

{

WSADATA wsaData;

SOCKET sConnect;

SOCKADDR_IN serverAddr;

int recvbytes;

char buf[MAXDATASIZE];

//初始化Windows Socket 2.2

WSAStartup(MAKEWORD(2,2), &wsaData);

// 创建一个新的Socket来连接服务器

sConnect = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// 填写连接地址信息

serverAddr.sin_family = AF_INET;

serverAddr.sin_port = htons(SERVPORT);

serverAddr.sin_addr.s_addr = inet_addr(SERVIP);

memset(&(serverAddr.sin_zero), 0, sizeof(serverAddr.sin_zero));

// 向服务器发出连接请求

if (connect(sConnect, (SOCKADDR *)&serverAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)

{

printf("connect failed!\n");

return;

}

// 接受服务器的回应消息

recvbytes = recv(sConnect, buf, MAXDATASIZE, 0);

if (recvbytes == SOCKET_ERROR)

{

printf("recv failed!\n");

}

else

{

buf[recvbytes] = '\0';

printf("%s\n",buf);

}

closesocket(sConnect);

// 释放Windows Socket DLL的相关资源

WSACleanup();

}



四, 结束语

这里介绍的只不过是winsock最基础最基础的知识,开发网络程序从来都不是一件容易的事情,

尽管只需要遵守很少的一些规则: 创建套接字,发起连接,接受连接,

发送和接受数据。真正的困难在于:让你的程序可以适应从单单一个连接到几千个连接,

即开发一个大容量,具可扩展性的Winsock应用程序。

Winscok编程很重要的一部分是IO处理,分别提供了“套接字模式”和“套接字I/O模型”,

http://blog.163.com/cuijinquan@126/blog/static/11901493201061112042297/

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
环境:Windows XP SP 3、 VC++ 6.0、 Windows Server 2003 SDK 使用步骤: 1、下载解压之后,使用VC++ 6.0 IDE打开.dws文件 2、点击“!”按钮运行程序 3、如果一切正常会出现一个对话框,然后选择下拉框中的“服务端”启动服务器进程--接着点击“监听”按钮 4、然后再点击“!”按钮运行客户端进程 5、如果一切正常,那么选择下拉框中的“客户端”启动客户端进程--接着点击“连接”按钮,让客户端连接到socket服务器 6、在客户端的“消息”栏中输入信息,然后点击“发送”按钮 7、点击服务器端进程,应该可以看见客户端发送过来的消息,然后可以在“消息”栏中输入响应信息,然后点击“发送”按钮,此时会在客户端进程中看服务器端进程响应的信息 该示例是实现了一个客户端对象对应一个服务端对象的socket的网络编程--最简单的VC++的网络编程示例。该示使用主要是自定义一个类--它继承MFC库中CAsyncSocket类,然后重写OnAccept, OnSend, OnReceive和OnClose方法来实现一对一的VC++的socket网络编程。注意:自定义类关联应用向导生成的对话框类,需要在头文件中书写语句“class CHelloSocketDlg;”,并且在.cpp文件中写入语句“#include "HelloSocketDlg.h";”,否则编译不会通过! 本示例使用Dialog模式的UI是为方便说明socket编程,在对话框类的OnInitDialog方法有初始化的成员变量的注释说明,在自定义类中的注释非常详细。本人觉得MFC使用异步通信类CAsyncSocket实现Java中的ServerSocket类和Socket类的编程效果,是非常牛的!(虽然本人认为它还封装得不够OO--因为它封装得让使用者觉得“很傻很天真”)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值