使用 Socket 的程序在使用 Socket 之前必须调用 WSAStartup() 函数,

此函数在应用程序中用来初始化 Windows Socket DLL,

只有此函数调用成功后,应用程序才可以再调用 Windows Sockets DLL 中的其他 API 函数,

否则后面的任何函数都将调用失败。

WSAStartup() 函数原型:

int WSAStartup(

    WORD wVersionRequested, // 应用程序欲使用的 Windows Socket 版本号。 
// 其高位字节是次版本号,低位字字节是髙版本号。可以使用宏 MAKEWORD(X,Y)来设置其版本号,x 表示高位字节,y 表示低位字节。
LPWSADATA lpWSAData // 指向 WSADATA 的指针。用来存储 Winsock 动态库信息。 );

函数调用后返回一个 int 型的值,通过检查这个值来确定初始化是否成功。该函数执行成功后返回 0。

在程序中调用该函数的形式如下:

WSAStartup( MAKEWORD(2,2), (LPWSADATA) &WSAData)

其中 MAKEWORD(2,2) 表示程序使用的是 WinSocket 2 版本,WSAData 用来存储系统传回的关于 WinSocket 的结构。

建立Socket:

初始化 WinSock 的动态连接库后,需要在服务器端建立一个用来监听的 Socket 句柄,为此可以调用 Socket() 函数,

用来建立这个监听的 Socket 句柄,并定义此 Socket 所使用的通信协议。

Socket 的原型:

SOCKET socket(

     int domain,  // 指定应用程序使用的通信协议的协议族,对于 TCP/IP 族,该函数设置为 AF_INET。

     int type,   // 指定要创建的套接字类型。

     int protocol  // 指定应用程序所使用的通信协议。

);

在Winsock 2 中,type 支持以下三种类型:

SOCK_STREAM : 流式套接字。

SOCK_DGRAM : 数据报套接字。

SOCK_RAW:原始套接字。

在Winsock 2 中,protocol 支持以下3种类型:

IPPROTO_UDP : UDP 协议,用于无连接数据报套接字。

IPPROTO_TCP : TCP 协议,用于流套接字。

IPPROTO_ICMP : ICMP 协议,用于原始套接字。

该函数调用成功则返回 Socket 对象,函数调用失败则返回 INVALID_SOCKET(调用 WSAGetLastError() 

可得知函数调用失败的原因,所有 WinSocket 的函数都可以使用这个函数来获取失败的原因)

在 Windows 程序中,并不是用内存的物理地址来标志内存块,文件,任务和动态装入模块;

相反,Windows API 给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。

提示:

句柄是 Windows 用来标志被应用程序所建立或使用的对象的唯一整数,Windows 使用各种各样的句柄标志,

诸如应用程序,窗口,控件,位图,GDI 对象等。一个Windows 应用程序可以用不同的方法获得一个特定项的句柄。

通常,Windows 通过应用程序的引出函数将一个句柄作为参数传递给应用程序,应用程序一旦获得了一个确定的句柄,

便可以在 Windows 环境下的任何地方对这个句柄进行操作。

绑定端口:

当创建了一个 Socket 以后,套接字的数据结构里有一个默认的 IP 地址和默认的 端口号。

一个服务器端程序必须调用 bind() 函数来为其绑定一个 IP 地址和一个特定的 端口号。

这样客户端才知道连接服务器的哪一个 IP 地址的哪一个 端口。

客户端程序一般不必调用 bind() 函数来为其 Socket 绑定 IP 地址和 端口号。

该函数调用成功返回 0 ,否则返回 SOCKET_ERROR.

bind() 函数原型:

int bind(

    SOCKET s,      // 指定待绑定的 Socket 描述符。

    CONST struct sockaddr FAR *name,  // 指定一个 sockaddr 结构。

    int namelen   // name 结构体的大小。

);

这里需要简单介绍下第二个参数 name,这个参数是一个 socket 结构体类型,socket 结构类型定义如下。

struct sockaddr(

      u_short sa_family;  //指定地址族,对于 TCP/IP 族的套接字,将其设置为 AF_INET.

      char sa_data[];

);

对于 TCP/IP 族的套接字进行绑定时,通常使用另一个地址结构:

struct sockaddr_in(

      short sin_family;    // 设置 TCP/IP 协议族类型 AF_INET。

      u_short sin_port;    // 指明端口号。

      struct in_addr sin_addr;   // 指明 IP 地址,该字段为整数。

                               //一般用 inet_addr()函数把字符串形式的 IP,转换成 unsigned long 型整数值后再发送给 s_addr。

      char sin_zero[];

);

in_addr是一个结构体,可以用来表示一个32位的IPv4地址。

结构体里面只有一个 s_addr 变量,用来存储 IPv4 地址。

bind() 函数调用示例:

//...   

     struct sockaddr_in name;

     name.sin_family = AF_INET;

     name.sin_port = htonl(INADDR_ANY);

     name.sin_addr.s_addr = htonl(INADDR_ANY);

     int namelen = sizeof(name);

     bind(sSocket, (struct sockaddr *) &name, namelen);

//...

htonl函数:

将主机的unsigned long值转换成网络字节顺序(32位)(一般主机跟网络上传输的字节顺序是不通的,分大小端),函数返回一个网络字节顺序的数字。

网络字节序与主机字节序:

主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,

这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

  a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

  b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。

这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。

字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,

而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,

导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。

记忆这类函数,主要看前面的n和后面的hl。n代表网络,h代表主机host,l代表long的长度,还有相对应的s代表16位的short。

以上代码中,如果不需要特别指明 IP 地址和 端口号,那么可以设定 IP 地址为 INADDR_ANY, Port 为 0。

Windows Sockets 会自动为其设定适当的 IP 地址及 Port(1024~5000)。

如果想得到 IP 地址和 端口号,可以调用 getsockname() 函数来获知其被设定的值。

监听端口:

服务器端的 Socket 对象绑定完成后,服务端必须建立一个监听的队列,来接收客户端发来的连接请求。

listen() 函数使服务器端的 Socket 进入监听状态,并设定可以建立的最大连接数。

listen() 函数原型:

int listen(

     SOCKET S,       // 指定监听的 Socket 描述符。

     int backlog     // 为一次同时连接的最大数目(不可超过 5)

);

该函数调用成功后返回 0,否则返回 SOCKET_ERROR。服务器端的 Socket 调用完 listen() 函数后,

使其套接字 s 处于监听状态,处于监听状态的流套接字 s 将维护一个客户连接请求队列。最多容纳 backlog 个客户请求。

创建服务器端接收客户端请求:

当客户端发出连接请求时,服务器端 hwnd 视窗会收到 Winsock stack 送来自定义的一个消息。

为了使服务器端接收客户端的连接请求,就要使用 accept() 函数。处于监听状态的流套接字 s

从客户端连接请求队列中取出排在最前面的一个客户请求,并且创建一个新的套接字来与客户端

套接字共同创建连接通道。原来处于监听状态的套接字继续处于监听状态,等待客户端的连接,

这样服务器端和客户端才算正式完成通信程序的连接动作。如果创建连接通道成功,

就返回新创建套接字的描述符,以后与客户端套接字交换数据的是新创建的套接字;

如果失败就返回 INVALID_SOCKET。

accept() 函数原型:

SOCKET accept(

      SOCKET s,         // 处于监听状态的流套接字。

      struct sockaddr FAR *addr, // 用来返回新创建的套接字的地址结构。

      int FAR *addrlen    // 指明用来返回新创建的套接字的地址结构的长度。

);

accept() 函数示例:

//...

     struct sockaddr_in addr;

     int addrlen;

     addrlen = sizeof(addr);

     accept(sSocket,(struct sockaddr *)&addr,&addrlen);

//...

服务器端响应客户端连接请求:

当客户端向服务器端发出连接请求,服务端即可用 accept() 函数实现与客户端建立连接,为了达到

服务端的 Socket 在恰当的时候与从客户端发来的连接请求建立连接,服务端需要使用 WSAAsyncSelect() 函数,

让系统主动来通知服务器端程序有客户端提出连接请求了。

该函数调用成功返回 0,否则返回 SOCKET_ERROR。

WSAAsyncSelect() 函数原型:

int WSAAsySelect(

    SOCKET s,         // Socket 对象。

    HWND hwnd,        // 接收消息的窗口句柄。

    unsigned int wMsg,  // 传给窗口的消息。

    long lEvent   // 被注册的网络事件,即是应用程序向窗口发送消息的网络事件。

);

lEvent 值为下列组合:

FD_READ:希望在套接字 S 收到数据时收到消息。

FD_WRITE:希望在套接字 S 发送数据时收到消息。

FD_ACCEPT:希望在套接字 S 收到连续请求时收到消息。

FD_CONNECT:希望在套接字 S 连接成功时收到消息。

FD_CLOSE:希望在套接字 S 连接关闭时收到消息。

FD_OOB:希望在套接字 S 收到带外数据时收到消息。

该函数在具体应用时,wMsg 应是在应用程序中定义的消息名称,而消息结构中的 IParam 则为以上各种网络事件名称。

所以,可以在窗口处理自定义消息函数中使用以下结构代码来响应 Socket 的不同事件。

switch(lParam)
{
case FD_READ:
......
break;
case FD_WRITE:
......
break;
case FD_ACCEPT:
......
break;
//...
}

完成服务端与客户端 Socket 连接:     

结束服务器端与客户端的通信连接,可以由服务器端或客户端的任一端发出请求,只要调用 closesocket() 就可以了。

同样的,要关闭服务器端监听状态的 Socket,也是利用该函数。在调用 closesocket() 函数关闭 Socket 之前,

与程序启动时调用 WSAStartup() 函数相对应。程序结束前,需调用 WSACleanup() 来通知  Winsock Stack释放 Socket 所占用的资源。

该函数调用成功后返回 0,失败返回 SOCKET_ERROR。

WSACleanup()函数原型:

int WSACleanup();

该函数在应用程序完成对请求的 Socket 库的使用后调用,来解除与 Socket 库的绑定并且释放 Socket 库

所占用的资源。该函数一般用在网络程序结束的地方调用。

closesocket() 函数原型:

int closesocket(

    socket s    // 表示关闭的套接字。

);

该函数如果成功执行就返回 0,否则返回 SOCKET_ERROR。

每个进程中都有一个套接字描述表,表中的每个套接字描述符都对应了一个位于操作系统缓存区的套接字数据结构。

因此,可能有几个套接字描述符指向同一个套接字数据结构。套接字数据结构中专门有一个字段存放该结构被引用的次数,

即有多少个套接字描述符指向该结构。当调用 closesocket() 函数时,操作系统先检查套接字数据结构中的该字段的值。

如果值为 1,就表明只有一个套接字描述符指向它,因此操作系统就先把 s 在套接字描述符表中对应的那条表项清除,

并且释放 s 对应的套接字数据结构;如果该字段值大于 1,那么操作系统仅仅清除 s 在套接字描述符表中的对应表项,

并且把 s 对应的套接字数据结构的引用次数减 1。

// 服务端
#include<stdio.h>
#include<winsock.h>
#pragma comment(lib,"ws2_32")
#define MYPORT 1234
#define BACKLOG 10
int main()
{
 SOCKET sockfd, new_fd;
 struct sockaddr_in my_addr;
 struct sockaddr_in their_addr;
 int sin_size = sizeof(struct sockaddr_in);
 int num;
 char Buffer[MAX_PATH];
 WSADATA ws;
 if (WSAStartup(MAKEWORD(2, 2), &ws) != 0)
    {
     exit(0);
    }
 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
    {
     exit(0);
    }
 my_addr.sin_family = AF_INET;
 my_addr.sin_port = htons(MYPORT);
 my_addr.sin_addr.S_un.S_addr = INADDR_ANY;
 if (bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1)
    {
     closesocket(sockfd);
     exit(0);
    }
 if (listen(sockfd, BACKLOG) == SOCKET_ERROR)
    {
     closesocket(sockfd);
     exit(0);
    }
 if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == INVALID_SOCKET)
    {
     closesocket(sockfd);
     exit(0);
    }
 printf("\nRequest Has Been Accept!\n");
 printf("\tClient ip:%s\t", inet_ntoa(their_addr.sin_addr));
 printf("\tClient Port:%d\n", ntohs(their_addr.sin_port));
 num = recv(new_fd, Buffer, MAX_PATH, 0);
 Buffer[num - 1] = '\0';
 printf("Msg:%s\n", Buffer);
 closesocket(sockfd);
 closesocket(new_fd);
 WSACleanup();
 getchar();
 return 0;
}
//在建立 socket, accept 并且 判断时,不要写成了这样:
if (sockfd = socket(AF_INET, SOCK_STREAM, ) == INVALID_SOCKET)
{
exit();
}
if (new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size) == INVALID_SOCKET)
    {
     closesocket(sockfd);
     exit(0);
    }
// 因为这 = 优先级比 == 低,所有这样写会建立或返回无效的套接字。
// 用 WSAGetLastError() 会返回 10038 错误代码。

服务器端 SOCKET 编程的更多相关文章

  1. 基于TCP的客户端、服务器端socket编程

    一.实验目的 理解tcp传输客户端服务器端通信流程 二.实验平台 MAC OS 三.实验内容 编写TCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦连接成功,则显示客户的IP地址.端口号,并 ...

  2. java基础之Socket编程概述以及简单案例

    概述: 用来实现网络互连的 不同的计算机上 运行的程序间 可以进行数据交互  也就是用来在不同的电脑间, 进行数据传输. 三大要素: IP地址: 设备(电脑,手机,ipad)在网络中的唯一标识. 组成 ...

  3. Python Socket 编程——聊天室示例程序

    上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的 ...

  4. python网络编程-socket编程

     一.服务端和客户端 BS架构 (腾讯通软件:server+client) CS架构 (web网站) C/S架构与socket的关系: 我们学习socket就是为了完成C/S架构的开发 二.OSI七层 ...

  5. Socket编程实践(2) Socket API 与 简单例程

    在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程.该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端. socket()函 ...

  6. [转]C语言SOCKET编程指南

    1.介绍 Socket编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等 ...

  7. socket编程-java

    一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...

  8. socket编程基础

    socket编程 什么是socket 定义 socket通常也称作套接字,用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过套接字向网络发出请求或者应答网络请求. socket起源于Unix ...

  9. Linux Socket编程

    “一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. ——有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览 ...

随机推荐

  1. 《Java 8 in Action》Chapter 10:用Optional取代null

    1965年,英国一位名为Tony Hoare的计算机科学家在设计ALGOL W语言时提出了null引用的想法.ALGOL W是第一批在堆上分配记录的类型语言之一.Hoare选择null引用这种方式,& ...

  2. Docker下kafka学习三部曲之一:极速体验kafka

    Kafka是一种高吞吐量的分布式发布订阅消息系统,从本章开始我们先极速体验,再实战docker下搭建kafka环境,最后开发一个java web应用来体验kafka服务. 我们一起用最快的速度体验ka ...

  3. 应用上下文webApplicationContext

    一.先说ServletContext javaee标准规定了,servlet容器需要在应用项目启动时,给应用项目初始化一个ServletContext作为公共环境容器存放公共信息.ServletCon ...

  4. Elasticsearch(8) --- 聚合查询(Metric聚合)

    Elasticsearch(8) --- 聚合查询(Metric聚合) 在Mysql中,我们可以获取一组数据的 最大值(Max).最小值(Min).同样我们能够对这组数据进行 分组(Group).那么 ...

  5. 微信支付中分账功能 填坑指南V1

    公司是做电商的,近期开发了一款小程序,准备线上线下同步销售玩具.这里就涉及到微信支付的功能,网上有很多教程,官方也有文档和Demo,因此微信支付还是比较容易实现的. 由于我们公司是和其他公司合作运营的 ...

  6. java8 新特性精心整理

    前言 越来越多的项目已经使用 Java 8 了,毫无疑问,Java 8 是Java自Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库.工具和 JVM 等方面的十多个新特 ...

  7. [VB.NET Tips]对于基本数据类型的提示

    1.类型字符 有时需要直接量后面加上类型字符以明确指定类型,下面把常用的类型字符列出来 类型 字符 Short S Integer I Long L Decimal D Char c Single F ...

  8. PacMan 01——玩家移动

    版权申明: 本文原创首发于以下网站: 博客园『优梦创客』的空间:https://www.cnblogs.com/raymondking123 优梦创客的官方博客:https://91make.top ...

  9. JavaScript之数学对象Math

    Javascript 中Math和其他对象不同,它具有数学常数和函数的属性和方法.因为它的属性是数学常数,所以不能被改变(可以进行赋值操作,但最后值不变). Math的方法就是普通函数,调用他们直接用 ...

  10. Unity3D-游戏场景优化之遮挡剔除(Occlusion Culling)的使用

    在大型3D游戏场景中,如何优化游戏性能是非常重要的一步.一般遮挡剔除是非常常用的.接下来我们看看如何使用遮挡剔除. 假设这是一个游戏场景. 下面这是相机的视口,相机的视觉是看不到很大立方体后面的那些小 ...