TCP/IP 的 socket 编程(三)

匿名 (未验证) 提交于 2019-12-02 23:43:01

  一、TCP/IP解析

TCP/IP协议的核心部分是传输层协议(TCP、UDP),网络层协议(IP)和物理接口层,这三层通常是在操作系统内核中实现。因此用户一般不涉及。编程时,编程界面有两种形式:

  1.1、是由内核直接提供的系统调用;

  1.2、使用以库函数方式提供的各种函数。

  前者为核内实现,后者为核外实现。用户服务要通过核外的应用程序才能实现,所以要使用套接字(socket)来实现。

二、TCP/IP服务器及客户端操作流程

2.1服务器操作流程

  

此函数在应用程序中初始化 Windows Sockets DLL,只有此函数调用成功后,应用程序才可以再调用其他Windows Sockets DLL 中的 API 函数。该函数原型

int WSAStartup(WORD wVersionRequested,  LPWSADATA lpWSAData);

参数:

 

 WORD wVersionRequested;     wVersionRequested = MAKEWORD(2, 0);  

返回值:

  a) 等于0,初始化成功;

  b)不等于0, 初始化失败;

  初始化 WinSockt 的动态连接库后,需要在服务器端建立一个监听的 Socket,为此可以调用 Socket()函数用来建立这个监听的 Socket,并定义此 Socket 所使用的通信协议.此函数调用成功返回 Socket 对象,失败则返回 INVALID_SOCKET.调用 WSAGetLastError()可得知原因,所有 WinSocket 的 API 函数都可以使用这个函数来获取失败的原因。函数原型如下:

SOCKET socket(int af,int type,int protocol)

参数:

如果要建立的是遵从 TCP/IP 协议的 socket,第二个参数 type 应为 SOCK_STREAM,如为 UDP(数据报)的socket,应为 SOCK_DGRAM。sockets(套接字)编程有三种:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW)。基于 TCP 的 socket 编程是采用的流式套接字。

返回值:SOCKET 类型

  2.1.3绑定端口(blind)

  将创建的套接字绑定到本机地址的某一端口上,接下来要为服务器端定义的这个监听的 Socket 指定一个地址及端口(Port),这样客户端才知道待会要连接哪一个地址的哪个端口,为此要调用 bind()函数,该函数调用成功返回 0,否则返回 SOCKET_ERROR.

int bind( SOCKET s,const struct sockaddr FAR *name,int namelen);

参 数:

a)s:被绑定的套接字

c) namelen:第二个参数name 的长度;

  如果使用者不在意地址或端口的值,那么可以设定地址为 INADDR_ANY,及 Port 为 0。对于多接口主机使用INADDR_ANY指定了一个通配地址,让该主机的任何一个IP地址都匹配。Windows Sockets会自动将其设定适当之地址及 Port(1024 到 5000 之间的值)。此后可以调用 getsockname()函数来获知其被设定的值。

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

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;    };  

addrServer.sin_addr = inet_addr("192.168.0.2");  

(2) sockaddr 类型

sockaddr 类型是用来表示 Socket 地址的类型,同 socketaddr_in 类型相比,sockaddr 的适用范围更广。

TCP/IP 地址。sockaddr 的定义如下:

struct sockaddr   {        ushort  sa_family;        char    sa_data[14];   }; 

可知sockaddr 的16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockadddr的。事实上也往往使用这种方法。

  2.1.4为套接字设置监听模式,准备客户请求。

  当服务器端的 Socket 对象绑定完成之后,服务器端必须建立一个监听的队列来接收客户端的连接请求.listen()函数使服务器端的 Socket 进入监听状态,并设定可以建立的最大连接数(目前最大值限制为 5,最小值为 1).该函数调用成功返回 0,否则返回 SOCKET_ERROR。

int listen(SOCKET s,int backlog );

:

a) s:需要建立监听的 Socket;

b) backlog:最大连接个数;

接受连接请求

  当 Client 提出连接请求时,Server 端 hwnd 视窗会收到 Winsock Stack 送来自定义的一个消息,这时可以分析 lParam,然后调用相关的函数来处理此事件。为了使服务器端接受客户端的连接请求,就要使用accept()函数,该函数新建一 Socket 与客户端的 Socket 相通,原先监听之 Socket 继续进入监听状态,等待他人的连接要求。该函数调用成功返回一个新产生的 Socket 对象,否则返回 INVALID_SOCKET。

SOCKET accept(SCOKET s,struct sockaddr FAR *addr,int FAR *addrlen );

:

a) s: 监听套接字

b) addr:存放来连接的客户端的地址、端口信息;

c) addrlen:addr 的长度

新返回的套接字和客户端进行通信。send/recv

send函数通过一个已建立连接的套接字发送数据,函数声明如下:

int send(SOCKET s, const char FAR *buf, int len, int flags);

:

a) s: 是一个已建立连接的套接字。

b) buf:指向一个缓冲区,该缓冲区包含将要传递的数据。

c) len:缓冲区的长度。

d) flags:收发数据方式的标识,如果不需要特殊要求可以设置为0。

send函数向客户端发送数据,注意这个函数使用的套接字需要使用已建立连接的那个套接字,而不是用于监听的那个套接字。

recv函数从一个已连接的套接字接收数据。函数原型如下:

int recv(SOCKET s,char FAR* buf, int len, int flags);

参数:

a) s:建立连接之后准备接收数据的那个套接字。

b) buf:指向缓冲区的指针,用来保存接收的数据。

c)len:缓冲区的长度。

d)flags:收发数据方式的标识,如果不需要特殊要求可以设置为0。

发送recv函数,应注意该函数的第一个参数也应该是建立连接之后的那个套接字并且定义一个字符数组recvBuf,用来保存接收的数据。

关闭套接字

  结束服务器和客户端的通信连接是很简单的,这一过程可以由服务器或客户机的任一端启动,只要调用closesocket()就可以了,而要关闭 Server 端监听状态的 socket,同样也是利用此函数.另外,与程序启动时调用 WSAStartup()函数相对应,程式结束前,需要调用 WSACleanup()来通知 Winsock Dll 释放 Socket 所占用的资源.这两个函数都是调用成功返回 0,否则返回 SOCKET_ERROR

int PASCAL FAR closesocket( SOCKET s );

:

s:Socket 的识别码;

int PASCAL FAR WSACleanup( void );

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!