基于面向连接和无连接的socket编程

基于面向连接的socket编程
基于面向连接的socket编程就是基于TCP的socket编程。基于TCP的socket编程的服务器器端程序和客户端程序的流程如下:

服务器端程序
1、创建套接字(socket);
2、将套接字绑定到一个本地地址和端口上(bind);
3、将套接字设为监听模式,准备接受客户请求(listen);
4、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept);
5、用返回的套接字和客户端进行通信(send/recv);
6、返回,等待另一客户的请求;
7、关闭套接字。

客户端程序
1、创建套接字(socket);
2、向服务器发出连接请求(connect);
3、和服务器端进行通信(send/recv);
4、关闭套接字。

基于面向无连接的socket编程就是基于UDP的socket编程。基于UDP的socket编程的服务器器端程序和客户端程序的流程如下:

服务器端程序
1、创建套接字(socket);
2、将套接字绑定到一个本地地址和端口上(bind);
3、等待接收数据(recvfrom);
4、关闭套接字。

客户端程序
1、创建套接字(socket);
2、向服务器发送数据(sendto);
4、关闭套接字。

利用套接字编写基于TCP的网络程序的步骤;
1)加载套接字库
加载套接字库需要用到的函数为WSAStartup。这个函数的作用:a、加载套接字库 b、进行套接字库的版本协商,即确定我们要使用哪个版本的套接字库。
WSAStartup函数的原型:
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData)
 wVersionRequested:指定你所请求套接字库的版本号。高位字节指定所需要的Winsock库的副版本,而低位字节则是主版本。可用MAKEWORD(x,y)方便获得 wVersionRequested的正确值(x表示高位字节,y表示低位字节)。
 lpWSAData:指向WSADATA结构体的指针,这是一个返回值的参数。WSADATA结构体接收的是windows sockets实现的详细细节。
WSADATA结构体
struct WSAData {
   WORD wVersion;
   WORD wHighVersion;
   char szDescription[WSADESCRIPTION_LEN+1];
   char szSystemStatus[WSASYSSTATUS_LEN+1];
   unsigned short iMaxSockets;
   unsigned short iMaxUdpDg;
   char FAR * lpVendorInfo;
};


WSAStartup把第一个字段wVersion设成打算使用的WinSock版本,wHighVersion参数容纳是现有的WinSock库的最高版本。这两个字段中,高位字节代表的是WinSock副版本,而低位字节代表的是WinSock主版本。
如果WinSock.dll或底层网络子系统没有被正确初始化或没有被找到,WSAStartup将返回WSASYSNOTREADY。此外这个函数允许你的应用程序协商使用某种版本的WinSock规范。如果请求的版本等于或高于DLL所支持的最低版本,WSAStartup成员中的wVersion将包含你的应用程序应该使用的版本,它是DLL支持最高版本和请求版本中较小的那一个。反之,若请求的版本低于DLL所支持的最低版本,WSAStartup将返回WSAVERNOTSUPPORTED。
注意:对于每一个WSAStartup成功调用(成功加载WinSockDLL后),在最后都对应一个WSACleanUp调用,以便释放为该应用程序分配的资源,终止WinSock链接库的使用。

2)创建套接字

创建套接字的函数为socket,返回一个套接字的描述符。
函数原型:SOCKET socket(int af,int type,int protocol)
参数说明:af指定地址族,对于TCP/IP协议的套接字,它只能是AF_INET。type指定Socket类型,对于1.1版本的Socket,它只支持两种类型的套接字,SOCK_STREAM(基于TCP)指定产生流式套接字,SOCK_DGRAM(基于UDP)产生数据报套接字。
protocol与特定的地址家族相关的协议。如果指定为0,那么它就会根据地址格式和套接字类别,自动为你选择一个合适的协议。
注意:如果这个函数调用成功,将会返回一个新的SOCKET数据类型的套接字描述符。如果调用失败,将会返回一个INVALID_SOCKET。

3)绑定套接字
创建套接字之后,我们需要将这个套接字绑定到一个本地地址和端口上。绑定套接字,我们需要调用函数bind。
bind函数功能:将本地地址和一个套接字关联起来。
函数原型:int  bind( SOCKET s, const struct sockaddr FAR* name,int namelen);
参数说明:第一个参数指定要绑定的套接字,第二个参数指定了该套接字的本地地址信息,是指向sockaddr结构的指针变量,由于该地址结构是为所有的地址家族准备的,这个结构可能随所使用的网络协议不同而不同。第三个参数指定地址结构的长度。
sockaddr结构定义如下:
     struct sockaddr {
  unsigned short sa_family; 
  char sa_data[14]; 
  };
sockaddr的第一个字段sa_family指定该地址家族,在这里必须设为AF_INET。sa_data仅仅是表示要求一块内存分配区,起到占位的作用,该区域中指定与协议相关的具体地址信息。由于实际要求的只是内存区,所以对于不同的协议家族,用不同的结构来替换sockaddr。
注意:在TCP/IP中,我们可以用sockaddr_in结构替换sockaddr,以方便我们填写地址信息。除sa_family,sockaddr
是按照网络字节顺序表示的。
sockaddr_in结构定义如下:
struct sockaddr_in
{
short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
 
unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
 
struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
 
unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
 
};
sin_family表示地址族,对于IP地址, sin_family成员将一直是AF_INET;成员sin_port指定将要分配给套接字的端口;sin_addr给出的是套接字主机的IP地址;sin_zero只是一个填充数,使得sockaddr_in结构长度和sockaddr结构长度一样。如果函数调用成功,将返回0,如果调用失败,将返回SOCKET_ERROR。将IP地址指定为INADDR_ANY,允许套接字向任何分配给本地机器的IP地址发送或接收数据。
注意:sin_port和sin_addr按照网络字节顺序表示的。
in_addr结构的定义
typedef 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;
} IN_ADDR,  *PIN_ADDR,  FAR *LPIN_ADDR

4)设置套接字为监听模式
设置套接字为监听模式,调用的函数为listen。
函数原型: int listen(SOCKET s, int backlog)
参数说明:第一个参数为套接字描述符,第二个参数为等待连接队列的最大长度

5)服务端等待请求
服务端等待请求,调用的函数为accept。
函数原型:
SOCKET accept(
  __in          SOCKET s,
  __out         struct sockaddr* addr,
  __in_out      int* addrlen
);
参数说明:第一个参数为套接字描述符;第二个参数为返回值,包含了发起连接请求端的IP地址信息和端口信息;第三个参数也是返回值,表示的是返回地址结构的长度,第三个参数必须赋初值。

注意:accept函数在接受了客户端的连接请求后,会返回一个新的套接字连接描述符;然后我们利用这个套接字就可以和客户端进行通信,而先前的套接字仍然监听客户的连接请求。
6)通信
向客户端发送数据,通过send函数。
函数原型:
int send(
  __in          SOCKET s,
  __in          const char* buf,
  __in          int len,
  __in          int flags
);
参数说明:第一个参数为套接字描述符,此时的套接字为建立连接所对应的套接字;第二个参数为buffer,包含了将要被传送的数据;第三个参数表示了buffer中数据的长度;第四个参数的设置将影响send函数的调用行为,一般设置为0。

小提示:
inet_ntoa函数接受一个in_addr结构类型的参数并返回一个以点分十进制格式表示的IP地址字符串。

从客户端接受数据,通过recv函数。
函数原型:
int recv(
  __in          SOCKET s,
  __out         char* buf,
  __in          int len,
  __in          int flags
);
参数说明: 第一个参数为套接字描述符,为建立连接之后所对应的套接字;第二个参数为buf用来接收数据;第三个参数为buf的长度;第四个参数的设置将影响recv函数的调用行为,一般设置为0。
小提示:
closesocket(SOCKET S),关闭套接字,释放为套接字所分配的资源。

7)以上都是基于TCP服务端程序的编写,对于客户端。
客户端需要做的工作如下:a、加载套接字库;b、创建套接字;c、不要绑定套接字,直接连接服务器端。
其中连接服务器端需要利用函数connect,这个函数为相应的套接字建立一个连接。
函数原型:
int connect(
  __in          SOCKET s,
  __in          const struct sockaddr* name,
  __in          int namelen
);

参数说明:第一个参数为套接字;第二个参数为sockaddr结构体的指针,主要是用来设定你所连接服务器端的地址信息;第三个参数为地址结构体的长度。

建立连接后,客户端接收服务器端发送的数据以及可以向服务器端发送数据。
WSACleanup(),终止套接字库的使用。

利用套接字编写基于UDP的网络程序的步骤;
一、服务器端
1)加载套接字库
同上
2)创建套接字
同上
3)绑定套接字
同上
4)通信
服务器端接收客户端的数据,此时调用的函数的recvfrom。
函数原型:
int recvfrom(
  __in          SOCKET s,
  __out         char* buf,
  __in          int len,
  __in          int flags,
  __out         struct sockaddr* from,
  __in_out      int* fromlen
);
参数说明:
第一个参数为套接字;第二个参数为buf,用来接收数据;第三个参数为buf的长度;第四个参数的设置将影响recvfrom函数的调用行为,一般设置为0;第五个参数为地址结构体的指针,接收发送数据方的地址信息;第六个参数表示地址结构的长度。

注意:基于UDP网络编程服务端程序的编写比较简单,绑定好套接字之后,就可以接收数据了。

二、客户端
1)加载套接字库
同上
2)创建套接字
同上
3)通信
基于UDP网络编程的客户端程序不需要绑定套接字,直接就可以向服务端发送数据,此时调用sendto函数。
函数原型:
int sendto(
  __in          SOCKET s,
  __in          const char* buf,
  __in          int len,
  __in          int flags,
  __in          const struct sockaddr* to,
  __in          int tolen
);
函数说明:
第一个参数为套接字;第二个参数为buf,包含了将要被发送的数据;第三个参数为buf中数据的长度;第四个参数的设置将影响sendto函数的调用行为,一般设置为0;第五个参数为地址结构体的指针,主要用来设定目的套接字的地址信息;第六个参数为地址结构体的长度。
小提示:
inet_addr函数接受一个点分十进制形式的字符串作为其参数并返回一个适合分配给S_addr的u_long类型的数值。


你可能感兴趣的:(网络编程)