【SOCKET编程】基于C++的TCP/UDP的聊天室

socket【插座;接口】:其实是模拟打电话的过程,IP地址就是电话号码,socket就是电话拨通的一条线

socket有2种方式:

①面向连接的流方式  

【对应的协议】:TCP  (Transport Control Protocol)

像打电话一样,是需要建立通路确保连接才能发送和接收信息.

【特点】可靠,有重发机制.

【例子】FTP,Telent


②无连接的数据报文方式  

【对应的协议】:UDP  (User Datagram Protocol)

像寄快递一样,不一定先发货就先到,也不一定到了信息是齐全的.

【特点】速率高,无重发机制

【例子】实时语音,实时图像转送,广播


VC环境

使用封装好了的WINSOCK  API


第一步,初始化函数调用

int  WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
/***原型是int PASCAL FAR WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
//PASCAL 和 FAR已作废,可以不用详细了解.  ***/
//第一个是用哪个版本的API,/WORD是一个无符号短整型,相当于一个汉字=2个英文字母=2字节.(取值范围0~216-1)
//第二个参数则是一个 指针类型,它是指向 WSAData类型的
//调用成功会返回0.
要使用winsock的API则必须通过这个函数才能使用端口.

之后要说的都是用版本2的

※顺便一说,如果编译出现了这个错误error LNK2001: unresolved external symbol _WSAStartup@8

只需要在 工程->设置-> 连接->对象/库模块 这一栏填入ws2_32.lib,点"确定",就OK了~


第二步,建立端口

SOCKET socket(int af, int type,int proctocol);
//af是address family(地址族) ,一般填AF_INET表示在Internet的socket
//type 就是类型, 流方式是SOCK_STREAM,  数据报文方式是SOCK_DGRAM
//第三个是传输协议,一般填0,表示缺省,让他自己默认去
//返回类型是SOCKET,是WINSOCK分配给的Socket编号,当指针用就可以了.
//建立失败则返回INVALID_SOCKET(残疾缺口)
做了这一步之后相当于你买了一个插座.

而这个插座是TCP还是UDP就看你的参数了


第三步,绑定要通信的程序,成为服务器

int bind(SOCKET s, strut_ockaddr_in*  name,  int  namelen);
//s就是第二步成功买回的,不,是申请到的接口
//name 是指进程具体地址,就像具体电话号码一样,他的类型是一个结构体
//namelen就是位置的长度
IP地址就能确认要连的是哪台主机,而一个电脑又有多个程序,所以端口就用来确认是哪个程序

一个端口只能一个程序占用(当然TCP和UDP不冲突)

下面是地址结构体具体

struct sockaddr_in{
  short                        sin_family;             //地址族,同样是设为AF_INET;
  unsigned  short              sin_port;               //端口,取值0~65536,但是1024以下大多被占用了
  struct    in_add             sin_addr;               // IP地址,太长了,所以这里也是个结构体
  char                         sin_zero[8];            //8个空字符..只是为了和SOCKADDR大小相同好处理(2+2+4+8=16)
};  
下面是IP地址的结构体
struct in_addr{
   union{                     //结构联合体,可以理解为分组
         struct{
                unsigned  char  s_b1,s_b2, s_b3, s_b4;
                }S_un_b;
         struct{
                unsigned   short   s_w1,s_w2;
               }S_un_w;
         unsigned  long  S_addr;
       }S_un;
};

嗯哼,对于192.168.0.105

第一种赋值方式: s_b1=192,   s_b2=168,  s_b3=0,  s_b4=105;

第二种赋值方式:s_w1=(168<<8)|192;     s_w2=(105<<8)|0;

第三种赋值方式:S_addr=(105<<24)|(0<<16)|(168<<8)|192;

你看上面,为啥看起来好像反转了一样,怎么算都是(105.0.168.192)啊

没错,这就关系到字节序的问题了.

除了JAVA使用BIG_ENDIAN,其他几乎都是使用little_endian的

IP地址的数据报头也是little_endian


到此,前三步就足够建立一个服务端了~(其实每一步都只是一句话而已啊行不行啊)

我总结下:最简单的建一个服务端,端口为4000

sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=INADDR_ANY; //INADDR_ANY:本机IP地址
m_addr.sin_port=htons(4000);

int a1=WSAStartup(2, &wsaData );
if(a1==0)        //返回0则表示初始化成功
{
m_hSocket=socket(AF_INET, SOCK_STREAM ,0);
int a2=bind(m_hSocket, (LPSOCKADDR)&addr ,sizeof(addr));
}

而客户端的话,前两步就足够了,不需要绑定,只需要得到一个socket.



接下来到socket的功能,这里分为TCP和UDP

TCP

服务端等待连接

int  listen(SOCKET s,int backlog);
//s就是申请到的socket
//backlog可取1~5,表示可以有多少个人排队
//返回值可以看是否有出错

这里就好比如打电话,如果对方关机,就是打不通,会返回SOCKET_ERROR

如果对方正在通话中,忙线,你就需要等待.而允许有1~5个人等待,其余就直接拒接.


客户端请求连接

int connect(SOCKET s,struct sockaddr_in *name,  int  *namelen );

服务端接受连接

SOCKET accept(SOCKET s,struct sockaddr_in *addr,  int  *addrlen);
//s是绑定的服务端接口,建立后返回的是一个SOCKET,是用来会话的.原先socket还要继续监听其他客户端请求连接
//*addr 是客户端的地址,可以填NULL,只是用来确认来自哪里的连接

发送消息

int send(SOCKET s, char *buf ,int  len, int flags);
//s表示要发给谁的接口,如果服务端要发给某个客户端,则用accpet返回的那个
//buf 和 len 是数据包 和其大小.  flag一般取0
//错误时可以返回SOCKETERROR

接收信息

int recv(SOCKET s, char *buf ,int  len, int flags);
//s表示来信的接口,其余同上


UDP

发送消息

int sendto(SOCKET s, char *buf ,int len,  int  flags,  struct sockaddr_in  to,  int tolen);
//s 是发送方申请到的SOCK_DGRAM端口
//*buf 和 len 是要发送的消息及其长度
//flags依然是0
//to 和 tolen是要发给哪个地址及其长度.(基本就是要发给一个绑定好的服务器).

看起来其实就是TCP的connect(....) + send(...)


接收消息

int sendto(SOCKET s, char *buf ,int len,  int  flags,  struct sockaddr_in  from,  int  *fromlen);
//s 是发送方申请到的SOCK_DGRAM端口
//*buf 和 len 是要接收的消息及其长度,如果文件长度大于len,多出的部分就会扔掉的
//flags依然是0
//from 和 *tofrom是收到信息后,可以保存信息的来源地址及其长度.就是说from只是一个指针,里面没东西的.
看起来就像是TCP的accept(...) + recv(...)



有阻塞模式和异步模式.

阻塞模式是指一指等待对方的请求连接或者回复,不然的话就动不了.

异步模式则是则各种搞,收到消息了再来处理.所以个人偏好异步模式.

上面所说的都是正常的阻塞模式


下面说一下以异步模式

其实异步模式需要在申请了一个socket之后就去转换.

int WSAAsyncSelect(SOCKET s,  HWND  hwnd ,  unsigned  int  wMsg  ,  long  lEVENT);
//s就是要变为非阻塞的套接字
//hWnd是句柄, 何谓句柄,就是一个long而已,反正在MFC中就是用来标示每个元件的唯一ID.这里用于窗口的句柄,这样可以知道是该给哪个窗口处理信息
//wMsg是用户自定义的信息,就是遇到要发生的动作,就发送一个这样的信息,注意,信息需要定义#define   ,还要是WM_USER+100开始
//lEvent   这个是期望要发生什么动作时处理信息
返回值是0时正常,其他均为错误.

具体用法因为要加上MFC的元件比较容易明白,不然就长篇大论了.

可以下载下面的代码来看




最后,关闭接口

closesocket(SOCKET s);



最后总结

【SOCKET编程】基于C++的TCP/UDP的聊天室_第1张图片

其中红色的地方就是阻塞模式,要等待对方的回应


下面是用基于VC的MFC的TCP和UDP简单聊天室代码

点击下载

你可能感兴趣的:(网络开发,C++/C)