一、Socket通讯的基础知识
Socket通讯是两个计算机之间最基本的通讯方法,有TCP和UDP两种协议。关于这两种协议的区别,不少文章已有详述,这里,稍微总结一下:
1.TCP是面向连接的,是“流”式的,意即通讯两端建立了一个“数码流管”,该流无头无尾,接收端保证接收顺序,但不保证包的分割。
2.UDP是面向无连接的,是“包”式的,意即通讯两端自由发送数据包,接收端不保证接收顺序,但保证包的分割与发送端一致。
正是基于上述二者的不同,在编程上,它们的区别如下:对TCP连接,服务器端过程(bind->listen->accept->send/receive)与客户端不相同(connect->send/receive),对UDP连接,二者似乎更对等一些(服务器端仅需要bind)。
二、socket在windows下和linux下的区别
一些文章也已涉及,这里,也是综合一下,并加上自己的理解。
项目 | Windows | Linux |
主要头文件 | winsock.h/winsock2.h | sys/socket.h fcntl.h errno.h |
链接库 | ws2_32.dll/lib | 连接是使用参数:-lstdc 运行时需要libstdc++.so.5,可在/usr/lib目录中创建一个链接。 |
初始化及退出 | 初始化需要调用WSAStartup,退出需调用WSACleanup | 无 |
关闭Socket | closesocket | 与文件操作相同close |
Socket类型 | SOCKET | 与文件句柄相同int |
错误查看 | WSAGetLastError | 全局变量errno |
设置非阻塞模式 | int i=1 ioctlsocket(sockethandle,FIONBIO,&i) | fcntl(ockethandle,F_SETFL, O_NONBLOCK) |
send/recv函数最后一个参数 | 一般设置为0 | 可以有多种组合:MSG_NOSIGNAL,MSG_DONTWAIT,MSG_WAITALL |
send的异常 | 当连接断开,还发数据的时候,不仅send()的返回值会有反映,而且还会像系统发送一个异常消息,如果不作处理,程序会退 出。为此,send()函数的最后一个参数可以设置MSG_NOSIGNAL,禁止send()函数向系统发送异常消息。 | |
WSA宏 | 除了可以使用标准的socket函数外,微软自己有许多以WSA开始的函数,作为对标准socket函数的封装(可能微软感觉这些函数更好用一些吧) |
三、跨平台的Socket辅助程序
以下给出源代码。
sock_wrap.h代码如下,其中用到了platform.h,定义_WIN32_PLATFROM_和_LINUX_PLATFROM_两个宏。代码可能有一定的改动。
sock_wrap.h
#ifndef _SOCK_WRAP_H_ #define _SOCK_WRAP_H_ /*windows*/ #define _WIN32_PLATFORM_ /*linux*/ //#define _LINUX_PLATFROM_ #if defined(_WIN32_PLATFORM_) #include <winsock2.h> typedef SOCKET HSocket; #endif #if defined (_LINUX_PLATFORM_) #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> typedef int HSocket; #define SOCKET_ERROR (-1) #define INVALID_SOCKET 0 #endif typedef struct { int block; int sendbuffersize; int recvbuffersize; int lingertimeout; int recvtimeout; int sendtimeout; } socketoption_t; typedef struct { int nbytes; int nresult; } transresult_t; int InitializeSocketEnvironment(); void FreeSocketEnvironment(); void GetAddressFrom(sockaddr_in *addr, const char *ip, int port); void GetIpAddress(char *ip, sockaddr_in *addr); bool IsValidSocketHandle(HSocket handle); int GetLastSocketError(); HSocket SocketOpen(int tcpudp); void SocketClose(HSocket &handle); int SocketBlock(HSocket hs, bool bblock); int SocketTimeOut(HSocket hs, int recvtimeout, int sendtimeout, int lingertimeout); int SocketConnect(HSocket hs, sockaddr_in *paddr); int SocketBind(HSocket hs, int port); HSocket SocketAccept(HSocket hs, sockaddr_in *addr); int SocketListen(HSocket hs, int maxconn); void SocketSend(HSocket hs, const char *ptr, int nbytes, transresult_t &rt); void SocketRecv(HSocket hs, char *ptr, int nbytes, transresult_t &rt); void SocketTryRecv(HSocket hs, char *ptr, int nbytes, int milliseconds, transresult_t &rt); void SocketTrySend(HSocket hs, const char *ptr, int nbytes, int milliseconds, transresult_t &rt); void SocketClearRecvBuffer(HSocket hs); class CSockWrap { public: CSockWrap(int tcpudp); ~CSockWrap(); void SetAddress(const char *ip, int port); void SetAddress(sockaddr_in *addr); int SetTimeOut(int recvtimeout, int sendtimeout, int lingertimeout); int SetBufferSize(int recvbuffersize, int sendbuffersize); int SetBlock(bool bblock); HSocket GetHandle () { return m_hSocket;} void Reopen(bool bForceClose); int Connect(); void Close(); transresult_t Send(void *ptr, int nbytes); transresult_t Recv(void *ptr, int nbytes ); transresult_t TrySend(void *ptr, int nbytes, int milliseconds); transresult_t TryRecv(void *ptr, int nbytes, int milliseconds ); void ClearRecvBuffer(); HSocket m_hSocket; protected: sockaddr_in m_stAddr; int m_tcpudp; }; #endif
sock_wrap.cpp
#include "stdafx.h" #include <stdio.h> #include <string.h> #include "sock_wrap.h" #define INVALIDSOCKHANDLE INVALID_SOCKET #if defined(_WIN32_PLATFORM_) #include <windows.h> #pragma comment(lib,"ws2_32.lib") #define ISSOCKHANDLE(x) (x!=INVALID_SOCKET) #define BLOCKREADWRITE 0 #define NONBLOCKREADWRITE 0 #define SENDNOSIGNAL 0 #define ETRYAGAIN(x) (x==WSAEWOULDBLOCK||x==WSAETIMEDOUT) #define gxsprintf sprintf_s #endif #if defined(_LINUX_PLATFORM_) #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #define ISSOCKHANDLE(x) (x>0) #define BLOCKREADWRITE MSG_WAITALL #define NONBLOCKREADWRITE MSG_DONTWAIT #define SENDNOSIGNAL MSG_NOSIGNAL #define ETRYAGAIN(x) (x==EAGAIN||x==EWOULDBLOCK) #define gxsprintf snprintf #endif //配置地址 void GetAddressFrom(sockaddr_in *addr, const char *ip, int port) { memset(addr, 0, sizeof(sockaddr_in)); addr->sin_family = AF_INET; /*地址类型为AF_INET*/ if(ip) { addr->sin_addr.s_addr = inet_addr(ip); } else { /*网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址*/ addr->sin_addr.s_addr = htonl(INADDR_ANY); } addr->sin_port = htons(port); /*端口号*/ } //得到字符串形式的IP地址 void GetIpAddress(char *ip, sockaddr_in *addr) { unsigned char *p =(unsigned char *)( &(addr->sin_addr)); gxsprintf(ip, 17, "%u.%u.%u.%u", *p,*(p+1), *(p+2), *(p+3) ); } //得到错误代码 int GetLastSocketError() { #if defined(_WIN32_PLATFORM_) return WSAGetLastError(); #endif #if defined(_LINUX_PLATFORM_) return errno; #endif } //判断是否是Socket bool IsValidSocketHandle(HSocket handle) { return ISSOCKHANDLE(handle); } //关闭Socket void SocketClose(HSocket &handle) { if(ISSOCKHANDLE(handle)) { #if defined(_WIN32_PLATFORM_) closesocket(handle); #endif #if defined(_LINUX_PLATFORM_) close(handle); #endif handle = INVALIDSOCKHANDLE; } } //根据协议类型打开Socket HSocket SocketOpen(int tcpudp) { int protocol = 0; HSocket hs; #if defined(_WIN32_PLATFORM_) if(tcpudp== SOCK_STREAM) protocol=IPPROTO_TCP; else if (tcpudp== SOCK_DGRAM) protocol = IPPROTO_UDP; #endif hs = socket(AF_INET, tcpudp, protocol); return hs; } //连接Socket int SocketConnect(HSocket hs, sockaddr_in *paddr) { return connect(hs,(struct sockaddr *)paddr,sizeof(sockaddr_in)); } //绑定Socket int SocketBind(HSocket hs, int port) { sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.S_un.S_addr = INADDR_ANY; return bind(hs, (struct sockaddr *)&sin, sizeof(sockaddr_in)); } //监听Socket int SocketListen(HSocket hs, int maxconn) { return listen(hs,maxconn); } //接收Socket HSocket SocketAccept(HSocket hs, sockaddr_in *paddr) { #if defined(_WIN32_PLATFORM_) int cliaddr_len = sizeof(sockaddr_in); #endif #if defined(_LINUX_PLATFORM_) socklen_t cliaddr_len = sizeof(sockaddr_in); #endif return accept(hs, (struct sockaddr *)paddr, &cliaddr_len); } // // if timeout occurs, nbytes=-1, nresult=1 // if socket error, nbyte=-1, nresult=-1 // if the other side has disconnected in either block mode or nonblock mode, nbytes=0, nresult=-1 // otherwise nbytes= the count of bytes sent , nresult=0 //发送 void SocketSend(HSocket hs, const char *ptr, int nbytes, transresult_t &rt) { rt.nbytes = 0; rt.nresult = 0; if(!ptr|| nbytes<1) return; //Linux: flag can be MSG_DONTWAIT, MSG_WAITALL, 使用MSG_WAITALL的时候, socket 必须是处于阻塞模式下,否则WAITALL不能起作用 rt.nbytes = send(hs, ptr, nbytes, BLOCKREADWRITE|SENDNOSIGNAL); if(rt.nbytes>0) { rt.nresult = (rt.nbytes == nbytes)?0:1; } else if(rt.nbytes==0) { rt.nresult=-1; } else { rt.nresult = GetLastSocketError(); rt.nresult = ETRYAGAIN(rt.nresult)? 1:-1; } } // if timeout occurs, nbytes=-1, nresult=1 // if socket error, nbyte=-1, nresult=-1 // if the other side has disconnected in either block mode or nonblock mode, nbytes=0, nresult=-1 //接收 void SocketRecv(HSocket hs, char *ptr, int nbytes, transresult_t &rt) { rt.nbytes = 0; rt.nresult = 0; if(!ptr|| nbytes<1) return; rt.nbytes = recv(hs, ptr, nbytes, BLOCKREADWRITE); if(rt.nbytes>0) { return; } else if(rt.nbytes==0) { rt.nresult=-1; } else { rt.nresult = GetLastSocketError(); rt.nresult = ETRYAGAIN(rt.nresult)? 1:-1; } } // nbytes= the count of bytes sent // if timeout occurs, nresult=1 // if socket error, nresult=-1, // if the other side has disconnected in either block mode or nonblock mode, nresult=-2 void SocketTrySend(HSocket hs, const char *ptr, int nbytes, int milliseconds, transresult_t &rt) { rt.nbytes = 0; rt.nresult = 0; if(!ptr|| nbytes<1) return; int n; while(1) { n = send(hs, ptr+rt.nbytes, nbytes, NONBLOCKREADWRITE|SENDNOSIGNAL); if(n>0) { rt.nbytes += n; nbytes -= n; if(rt.nbytes >= nbytes) { rt.nresult = 0; break; } } else if( n==0) { rt.nresult= -2; break; } else { n = GetLastSocketError(); if(ETRYAGAIN(n)) { } else { rt.nresult = -1; break; } } } } // if timeout occurs, nbytes=-1, nresult=1 // if socket error, nbyte=-1, nresult=-1 // if the other side has disconnected in either block mode or nonblock mode, nbytes=0, nresult=-1 void SocketTryRecv(HSocket hs, char *ptr, int nbytes, int milliseconds, transresult_t &rt) { rt.nbytes = 0; rt.nresult = 0; if(!ptr|| nbytes<1) return; if(milliseconds>2) { while(1) { rt.nbytes = recv(hs, ptr, nbytes, NONBLOCKREADWRITE); if(rt.nbytes>0) { break; } else if(rt.nbytes==0) { rt.nresult = -1; break; } else { rt.nresult = GetLastSocketError(); if( ETRYAGAIN(rt.nresult)) { } else { rt.nresult = -1; break; } } } } else { SocketRecv(hs, ptr, nbytes, rt); } } //清理接收缓存 void SocketClearRecvBuffer(HSocket hs) { #if defined(_WIN32_PLATFORM_) struct timeval tmOut; tmOut.tv_sec = 0; tmOut.tv_usec = 0; fd_set fds; FD_ZERO(&fds); FD_SET(hs, &fds); int nRet = 1; char tmp[100]; while(nRet>0) { //检查是否可读 nRet= select(FD_SETSIZE, &fds, NULL, NULL, &tmOut); if(nRet>0) { nRet = recv(hs, tmp, 100,0); } } #endif #if defined(_LINUX_PLATFORM_) char tmp[100]; while(recv(hs, tmp, 100, NONBLOCKREADWRITE)> 0); #endif } //设置Socket阻塞或非阻塞模式 int SocketBlock(HSocket hs, bool bblock) { unsigned long mode; if( ISSOCKHANDLE(hs)) { #if defined(_WIN32_PLATFORM_) mode = bblock?0:1; /* 控制套接口的模式: FIONBIO:允许或禁止套接口s的非阻塞模式 FIONREAD:确定套接口s自动读入的数据量 SIOCATMARK:确实是否所有的带外数据都已被读入 */ return ioctlsocket(hs,FIONBIO,&mode); #endif #if defined(_LINUX_PLATFORM_) mode = fcntl(hs, F_GETFL, 0); //获取文件的flags值。 //设置成阻塞模式 非阻塞模式 return bblock?fcntl(hs,F_SETFL, mode&~O_NONBLOCK): fcntl(hs, F_SETFL, mode | O_NONBLOCK); #endif } return -1; } //Socket超时设置 int SocketTimeOut(HSocket hs, int recvtimeout, int sendtimeout, int lingertimeout) //in milliseconds { int rt=-1; if (ISSOCKHANDLE(hs) ) { rt=0; #if defined(_WIN32_PLATFORM_) if(lingertimeout>-1) { struct linger lin; lin.l_onoff = lingertimeout; lin.l_linger = lingertimeout ; rt = setsockopt(hs,SOL_SOCKET,SO_DONTLINGER,(const char*)&lin,sizeof(linger)) == 0 ? 0:0x1; } if(recvtimeout>0 && rt == 0) { rt = rt | (setsockopt(hs,SOL_SOCKET,SO_RCVTIMEO,(char *)&recvtimeout,sizeof(int))==0?0:0x2); } if(sendtimeout>0 && rt == 0) { rt = rt | (setsockopt(hs,SOL_SOCKET, SO_SNDTIMEO, (char *)&sendtimeout,sizeof(int))==0?0:0x4); } #endif #if defined(_LINUX_PLATFORM_) struct timeval timeout; if(lingertimeout>-1) { struct linger lin; lin.l_onoff = lingertimeout>0?1:0; lin.l_linger = lingertimeout/1000 ; rt = setsockopt(hs,SOL_SOCKET,SO_LINGER,(const char*)&lin,sizeof(linger)) == 0 ? 0:0x1; } if(recvtimeout>0 && rt == 0) { timeout.tv_sec = recvtimeout/1000; timeout.tv_usec = (recvtimeout % 1000)*1000; rt = rt | (setsockopt(hs,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout))==0?0:0x2); } if(sendtimeout>0 && rt == 0) { timeout.tv_sec = sendtimeout/1000; timeout.tv_usec = (sendtimeout % 1000)*1000; rt = rt | (setsockopt(hs,SOL_SOCKET, SO_SNDTIMEO, &timeout,sizeof(timeout))==0?0:0x4); } #endif } return rt; } //初始化Socket int InitializeSocketEnvironment() { #if defined(_WIN32_PLATFORM_) WSADATA Ws; //Init Windows Socket if ( WSAStartup(MAKEWORD(2,2), &Ws) != 0 ) { return -1; } #endif return 0; } //释放Socket void FreeSocketEnvironment() { #if defined(_WIN32_PLATFORM_) WSACleanup(); #endif } //============================================================================================================== //================================================================================================================ CSockWrap::CSockWrap(int tcpudp) { memset(&m_stAddr, 0, sizeof(sockaddr_in)); m_tcpudp = tcpudp; m_hSocket = INVALIDSOCKHANDLE; Reopen(false); } CSockWrap::~CSockWrap() { SocketClose(m_hSocket); } void CSockWrap::Reopen(bool bForceClose) { if (ISSOCKHANDLE(m_hSocket) && bForceClose) SocketClose(m_hSocket); if (!ISSOCKHANDLE(m_hSocket) ) { m_hSocket=SocketOpen(m_tcpudp); } } void CSockWrap::SetAddress(const char *ip, int port) { GetAddressFrom(&m_stAddr, ip, port); } void CSockWrap::SetAddress(sockaddr_in *addr) { memcpy(&m_stAddr, addr, sizeof(sockaddr_in)); } int CSockWrap::SetTimeOut(int recvtimeout, int sendtimeout, int lingertimeout) //in milliseconds { return SocketTimeOut(m_hSocket, recvtimeout, sendtimeout, lingertimeout); } int CSockWrap::SetBufferSize(int recvbuffersize, int sendbuffersize) //in bytes { int rt=-1; if (ISSOCKHANDLE(m_hSocket) ) { #if defined(_WIN32_PLATFORM_) if(recvbuffersize>-1) { rt = setsockopt( m_hSocket, SOL_SOCKET, SO_RCVBUF, ( const char* )&recvbuffersize, sizeof( int ) ); } if(sendbuffersize>-1) { rt = rt | (setsockopt(m_hSocket,SOL_SOCKET,SO_SNDBUF,(char *)&sendbuffersize,sizeof(int))==0?0:0x2); } #endif } return rt; } int CSockWrap::SetBlock(bool bblock) { return SocketBlock(m_hSocket, bblock); } int CSockWrap::Connect() { return SocketConnect(m_hSocket,&m_stAddr); } transresult_t CSockWrap::Send(void *ptr, int nbytes) { transresult_t rt; SocketSend(m_hSocket, (const char *)ptr, nbytes,rt); return rt; } transresult_t CSockWrap::Recv(void *ptr, int nbytes ) { transresult_t rt; SocketRecv(m_hSocket, (char *)ptr, nbytes,rt); return rt; } transresult_t CSockWrap::TrySend(void *ptr, int nbytes, int milliseconds) { transresult_t rt; SocketTrySend(m_hSocket, (const char *)ptr, nbytes,milliseconds, rt); return rt; } transresult_t CSockWrap::TryRecv(void *ptr, int nbytes, int milliseconds ) { transresult_t rt; SocketTryRecv(m_hSocket, (char *)ptr, nbytes,milliseconds, rt); return rt; } void CSockWrap::ClearRecvBuffer() { SocketClearRecvBuffer(m_hSocket); }
下面是测试代码
static UINT ClientThread(LPVOID pVoid) { HSocket client; client = SocketOpen(SOCK_STREAM); sockaddr_in serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port = htons(8888); serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (SocketConnect(client,&serAddr) == SOCKET_ERROR) { AfxMessageBox(L"连接失败"); SocketClose(client); return -1; } int buf_size = 1280*720*4+12; char *buf = new char[buf_size]; while (TRUE) { memset(buf,0,buf_size); transresult_t tt; SocketRecv(client,buf,buf_size,tt); test(buf); //OutputDebugStringA(buf); } AfxMessageBox(L"End"); return 0; } void CMFCDemoDlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 DWORD dwThreadId; HANDLE thread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ClientThread,this,0,&dwThreadId); CloseHandle(thread); } static UINT ServerThread(LPVOID pVoid) { HSocket server = SocketOpen(SOCK_STREAM); //绑定IP和端口 SocketBind(server,8888); SocketListen(server,5); OutputDebugStringA("等待连接...\n"); while(true) { sockaddr_in remoteAddr; HSocket client = SocketAccept(server,&remoteAddr); if(client == INVALID_SOCKET)continue; OutputDebugStringA("有客户端连接\n"); for (int i=0;i<10;i++) { transresult_t tt; char msg[12] = {0}; int w = 12; int h = 22; int sum = 12312; memcpy(msg,&w,4); memcpy(msg+4,&h,4); memcpy(msg+8,&sum,4); SocketSend(client,msg,12,tt); Sleep(1000); } } return 0; } void CMFCDemoDlg::OnBnClickedButton2() { // TODO: 在此添加控件通知处理程序代码 DWORD dwThreadId; HANDLE thread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ServerThread,NULL,0,&dwThreadId); CloseHandle(thread); }
收藏的用户(0) X
正在加载信息~
推荐阅读
站点信息
- 文章2299
- 用户1336
- 访客10618802
每日一句
You leave, or I go with you.
你留下,或者我跟你走。——《海角七号》
你留下,或者我跟你走。——《海角七号》
新会员