C++使用socket套接字的通信编程实现

最近在写一个小的服务器, 几百的并发, 边做边学习。

本文是转载 作者:心里正在嘀咕

原文地址: http://blog.sina.com.cn/s/blog_6af59f410101kcpz.html
程序代码:  链接: http://pan.baidu.com/s/1geMsmfL
                             http://pan.baidu.com/s/1kV4Pq2z
                             http://pan.baidu.com/s/1o8pQfTw
                             http://pan.baidu.com/s/1i5z3I77

程序使用说明:
1.可以直接进文件夹debug中双击*.exe运行程序。
2.要先运行服务器端server.exe在运行客户端client.exe。
3.聊天发送要轮流进行。



程序流程图:
C++使用socket套接字的通信编程实现



使用到的socket函数(搜集自网络):

socket()

创建一个套接口。 #include  SOCKET PASCAL FAR socket( int af, int type, int protocol); af:一个地址描述。目前仅支持AF_INET格式,也就是说ARPA Internet地址格式。 type:新套接口的类型描述。 protocol:套接口所用的协议。如调用者不想指定,可用0指定,表示缺省。
-----------------------------------------------------------------------------------------------------------
struct sockaddr_in

 {
    short int sin_family; 
    unsigned short int sin_port; 
    struct in_addr sin_addr;
    unsigned char sin_zero[8]; 
};

sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序)
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
s_addr按照网络字节顺序存储IP地址

此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
----------------------------------------------------------------------------------------------------------

WSAData
 
功能是:存放windows socket初始化信息.
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; 
}; 
  wVersion为你将使用的Winsock版本号,wHighVersion为载入的Winsock动态库支持的最高版本,注意,它们的高字节代表次版本,低字节代表主版本。
    szDescription与szSystemStatus由特定版本的Winsock设置,实际上没有太大用处。
    iMaxSockets表示最大数量的并发Sockets,其值依赖于可使用的硬件资源。
    iMaxUdpDg表示数据报的最大长度;然而,获取数据报的最大长度,你需要使用WSAEnumProtocols对协议进行查询。
    最大数量的并发Sockets并不是什么神奇的数字,它是由可用的物理资源来决定的.
    lpVendorInfo是为Winsock实现而保留的制造商信息,这个在Windows平台上并没有什么用处.

-------------------------------------------------------------------------------------------------------------------------------------------


WSAStartup()函数

函数原型
int WSAStartup
{
         WORD wVersionRequested,
         LPWSADATA lpWSAData
};
使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0.例:假如一个程序要使用2.1版本的Socket,那么程序代码如下:
wVersionRequested = MAKEWORD( 2, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
返回值:0表示成功。
-------------------------------------------------------------------------------------

int socket(int domain,int type,int protocol);

第一个参数domain设置为“AF_INET”。
第二个参数是套接口的类型:SOCK_STREAM或
SOCK_DGRAM。第三个参数设置为0。
系统调用socket()只返回一个套接口描述符,如果出错,则返回-1。
--------------------------------------------------------------------------------------

int connect(int sockfd,struct sockaddr* serv_addr,int addrlen);

第一个参数还是套接口文件描述符,它是由系统调用socket()返回的。
第二个参数是serv_addr是指向数据结构sockaddr的指针,其中包括目的端口和IP地址。
第三个参数可以使用sizeof(structsockaddr)而获得。

----------------------------------------------------------------------------------------------

void *memset(void *s,int c,size_t n)

总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。

----------------------------------------------------------------------------------------------

htons函数

htons函数将一个16位的无符号短整型数据由主机排列方式转换为网络排列方式。
-------------------------------------------------------------------------------------------------
htonl

htonl就是把本机字节顺序转化为网络字节顺序
所谓网络字节顺序(大尾顺序)就是指一个数在内存中存储的时候“高对低,低对高”(即一个数的高位字节存放于低地址单元,低位字节存放在高地址单元中)。但是计算机的内存存储数据时有可能是大尾顺序或者小尾顺序。
--------------------------------------------------------------------------------------------------

in_addr_t inet_addr

in_addr_t inet_addr(const char* strptr);
返回:若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址,否则为INADDR_NONE
struct in_addr{
in_addr_t s_addr;
}

----------------------------------------------------------------------------------------------------
connect 

  功能:建立与TCP服务器的连接 
  定义: 
  int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 
  //sockfd 是系统调用 socket() 返回的套接口文件描述符 
  serv_addr 是保存着目的地端口和 IP 地址的数据结构 struct sockaddr 
  //addrlen 设置为 sizeof(struct sockaddr) 
  connect 激发 TCP的三路握手过程 
  服务器必须准备好接受外来的连接。 

------------------------------------------------------------------------------------------------------

listen()

如果你希望不连接到远程的主机,也就是说你希望等待一个进入的连接请求,然后再处理它们。这样,你通过首先调用listen(),然后再调用accept()来实现。
系统调用listen()的形式如下:
int  listen(int sockfd,int backlog);

第一个参数是系统调用socket()返回的套接口文件描述符。
第二个参数是进入队列中允许的连接的个数。进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为20。你可以设置为5或者10。当出错时,listen()将会返回-1值。
当然,在使用系统调用listen()之前,我们需要调用bind()绑定到需要的端口,否则系统内核将会让我们监听一个随机的端口。所以,如果你希望监听一个端口,下面是应该使用的系统调用的顺序:
socket();
bind();
listen();
----------------------------------------------------------------------------------------------------------

accept()

在一个套接口接受一个连接。accept() 是c语言中网络编程的重要的函数,windows系统在#include ,而linux系统在#include 中。
文件包含
#include
原型
SOCKET PASCAL FAR accept( SOCKET s, struct sockaddr FAR* addr,int FAR* addrlen);
参数
s:套接口描述字,该套接口在listen()后监听连接。
addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。
返回值
成功返回一个新的套接字描述符,失败返回-1。

本函数从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄。如果队列中无等待连接,且套接口为阻塞方式,则accept()阻塞调用进程直至新的连接出现。如果套接口为非阻塞方式且队列中无等待连接,则accept()返回一错误代码。已接受连接的套接口不能用于接受新的连接,原套接口仍保持开放。


accept()函数 

       系统调用 accept() 会有点古怪的地方的!

       你可以想象发生 这样的事情:有人从很远的地方通过一个你在侦听 (listen()) 的端口连接 (connect()) 到你的机器。它的连接将加入到等待接受 (accept()) 的队列中。

       你调用 accept() 告诉它你有空闲的连接。它将返回一个新的套接字文 件描述符!这样你就有两个套接字了,原来的一个还在侦听你的那个端口, 新的在准备发送 (send()) 和接收 ( recv()) 数据。这就是这个过程! 

       函数是这样定义的: 


#include  
 
int accept(int sockfd, void *addr, int *addrlen);   

       sockfd 相当简单,是和 listen() 中一样的套接字描述符。
       addr 是个指向局部的数据结构 sockaddr_in 的指针。这是要求接入的信息所要去的地 方(你可以测定哪个地址在哪个端口呼叫你)。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

recev()

系统调用recv()的使用方法和send()类似:
int recv(int sockfd,  void* buf,  int len,  unsigned int flags);
第一个参数是要读取的套接口文件描述符。
第二个参数是保存读入信息的地址。
第三个参数是缓冲区的最大长度。第四个参数设置为0。
系统调用recv()返回实际读取到缓冲区的  --字节数---,如果出错则返回-1。
这样使用上面的系统调用,你可以通过数据流套接口来发送和接受信息。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

inet_ntoa

功能:
将一个IP转换成一个互联网标准点分格式的字符串。
原型:
char FAR * inet_ntoa( struct in_addr in);
头文件:
arpa/inet.h
Winsock2.h
参数:
一个网络上的IP地址
返回值:
如果正确,返回一个字符指针,指向一块存储着点分格式IP地址的静态缓冲区(同一线程内共享此内存);错误,返回NULL。

-----------------------------------------------------------------------------------------------------------

ntohs()

简述

将一个无符号短整形数从网络字节顺序转换为主机字节顺序。
#include
uint16_t ntohs(uint16_t netshort);
netshort:一个以网络字节顺序表达的16位数。
注释

本函数将一个16位数由网络字节顺序转换为主机字节顺序。
返回值

ntohs()返回一个以主机字节顺序表达的数。



------------------------------------------------------------------------------------------

 bind 函数 

  功能:给套接口分配一个本地协议地址 
  定义: 
  int bind(int sockfd, const struct sockaddr *my_addr, int addrlen); 
  sockfd 是调用 socket 返回的文件描述符。 
  my_addr 是指向数据结构 struct sockaddr 的指针,保存地址(即端口和 IP 地址) 信息。 
  addrlen 设置为 sizeof(struct sockaddr)。 
  返回: 0—成功, -1---出错 
  让内核自动处理地址ip和端口port 
  my_addr.sin_port = 0;  
  my_addr.sin_addr.s_addr = INADDR_ANY;  
  bind( ) 自己选择合适的端口:将0赋给 my_addr.sin_por。 
  自动填上他所运行的机器的 IP 地址:my_addr.sin_addr.s_addr 设置为 INADDR_ANY。 


一般情况下,如果你要建立网络服务器应用程序,则你要通知服务器操作系统:请在某地址 xxx.xxx.xxx.xxx上的某端口 yyyy上进行侦听,并且把侦听到的数据包发送给我。这个过程,你是通过bind()系统调用完成的。——也就是说,你的程序要绑定服务器的某地址,或者说:把服务器的某地址上的某端口占为已用。服务器操作系统可以给你这个指定的地址,也可以不给你。
---------------------------------------------------------------------------------------
WSACleanup() 

函数简述:
  中止Windows Sockets DLL的使用.
          #include
          int PASCAL FAR WSACleanup ( void );
注释:
       应用程序或DLL在使用Windows Sockets服务之前必须要进行一次成功的WSAStartup()调用.当它完成了Windows Sockets的使用后,应用程序或DLL必须调用WSACleanup()将其从Windows Sockets的实现中注销,并且该实现释放为应用程序或DLL分配的任何资源.任何打开的并已建立连接的SOCK_STREAM类型套接口在调用WSACleanup()时会重置; 而已经由closesocket()关闭却仍有要发送的悬而未决数据的套接口则不会受影响- 该数据仍要发送.

http://blog.sina.com.cn/s/blog_6b51e9370100uu8m.html

-------------------------------------------------------------------------------------


/////////////////////////////////////////////////////////////////////////////////////////////////////////////

int send(SOCKET s,  const char *buf,    int len,    int flags);
 
参数描述:
SOCKET s         发送端套接字描述符
const char *buf  应用程序要发送的数据的缓冲区(想要发送的数据)
int len          实际要发送的字节数
int flags        一般置为0即可

同步Socket的send函数的执行流程如下:

调用该函数时,send先比较待发送数据的长度len与套接字s的发送缓冲区的长度(区别于buf),如果len大于s的发送缓冲区的长度,则函数返回SOCKET_ERROR;
如果len小于或者等于s发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲区中的数据:                                                                                                        a.如果是在发送,就等待协议将数据发送完毕。                                                      b.如果没有开始发送s的缓冲区中的数据,那么send就比较s的发送缓冲区的剩余空间和len的大小:                  
如果len大于发送缓冲区剩余空间大小(不足放入剩余发送缓冲区),send就一直等待协议把s发送缓冲区中的数据发送完;
如果len小于发送缓冲区剩余空间大小,就仅仅把buf中的数据copy到发送缓冲区的剩余空间里(send函数返回时并不代表send把s的缓冲区的数据(buf)传到连接的另一端,而是协议传输的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间中)。 
 Socket中send函数的理解(一)如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时断开网络,那么send函数也返回SOCKET_ERROR。
要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间后就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR.(每一个除send之外的Socket函数在执行的最开始总要先等待套接字的发送缓冲区中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)。

猜你喜欢

转载自blog.csdn.net/u012414639/article/details/52537073