socket

Socket应用
C/C++ TCP-Client
1. 定义绑定IP的地址结构
struct sockaddr_in sinclient;
SOCKET c_Socket; //创建socket返回的描述字
2. 创建socket
c_Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
AF_INET:协议域,代表IPV4
SOCK_STREAM:协议类型,一般与IPPROTO_TCP配对
IPPROTO_TCP:TCP协议
3. 指定IP、端口连接
int port;
char host[64];
sinclient.sin_addr.s_addr = inet_addr(host); //指定所连接的IP
sinclient.sin_family = AF_INET;
sinclient.sin_port = htons(port); //指定所连接的IP端口

//设置套接字为非阻塞模式
#ifdef OS_WINDOWS //windows
u_long argp = 1;
if (ioctlsocket(c_Socket, FIONBIO, &argp) == SOCKET_ERROR)
#else //Linux
int flags;
flags = fcntl(c_Socket, F_GETFL, 0);
if (fcntl(c_Socket, F_SETFL, flags | O_NONBLOCK) < 0) //O_NDELAY

#endif
1) ioctlsocket参数说明
ioctlsocket函数头文件为#include <winsock.h>
int PASCAL FAR ioctlsocket (
_In_ SOCKET s,
_In_ long cmd,
_Inout_ u_long FAR *argp);
 s: 创建socket描述符
 cmd: FIONBIO 允许或禁止套接口s为非阻塞模式;
FIONREAD 确定套接口s自动读入的数据量;
SIOCATMARK 确认是否所有带外数据都已被读入。
 argp:1表示是非阻塞 0表示阻塞
2) fcntl参数说明
Linux下分两步完成非阻塞的设置
 F_GETFL:获取fd文件状态标志
 F_SETFL:设置flags描述符状态标志,O_NONBLOCK属于非阻塞IO

connect(c_Socket, (struct sockaddr *)& sinclient, sizeof(sinclient))<0表示连接成功
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, (socklen_t *)&len) == 0)
{
调用此函数获取任意类型、任意状态套接口的当前值存入error;
调用AGetLastError()函数打印error值;
}
4. send和recv调用
1) 先检测套接描述符是否可读、可写
fd_set fdwSet; //存储描述字集合
fd_set fdrSet;
struct timeval tv;//时间结构体
/*判断soket是否可写*/
FD_ZERO(&fdrSet); //清除集合
FD_SET(m_Socket, &fdrSet); //加入要监听的描述符
FD_ZERO(&fdwSet);
FD_SET(m_Socket, &fdwSet);
tv.tv_sec = 0;
tv.tv_usec = 10 * 1000;

len = select(m_Socket + 1, &fdrSet, &fdwSet, 0, &tv);//监测读、写的描述符
if (len > 0)
{
if (FD_ISSET(m_Socket, &fdwSet)) //可写
{
rtn = ::send(m_Socket , (char *)buff , len , 0);
}
if (FD_ISSET(m_Socket, &fdrSet)) //可读
{
len = ::recv(m_Socket , (char *)buff , 8000 , 0);
}
}
C/C++ TCP-Server
1. 定义绑定IP的地址结构
struct sockaddr_in sinserver;
SOCKET s_Socket; //创建socket返回的描述字
SOCKET a_Socket; //accept返回的连接描述字
2. 创建socket
s_Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
AF_INET:协议域,代表IPV4
SOCK_STREAM:协议类型,一般与IPPROTO_TCP配对
IPPROTO_TCP:TCP协议
3. 设置IP重复绑定
//设置IP可以重复绑定
if(setsockopt(s_Socket,SOL_SOCKET,SO_REUSEADDR,(char*)&on,sizeof(on))!=0)
{
设置失败!
continue;
}
4. 设置网络端口绑定
memset(&sin, 0, sizeof(sin));
sinserver.sin_family = AF_INET;
sinserver.sin_addr.s_addr = htonl((uint64)INADDR_ANY); //绑定本地IP
sinserver.sin_port = htons((unsigned short)m_ListenPort);//可以监听多个端口

rtn = ::bind(s_Socket, (struct sockaddr *)&sin, sizeof(sin)); //同一个端口只绑定一次
5. 监听客户端
rtn = listen(s_Socket, m_ListenNum); //可以监听多个客户端,但是一个收发线程管理一个连接

6. 接收客户端连接
fd_set rfds;
timeval timevalue;
timevalue.tv_sec = 0;
timevalue.tv_usec = 40000;
FD_ZERO(&rfds);
for(i=0;i<m_ListenPortSize;i++) //可以把多个描述符塞进集合进行监听
FD_SET(s_Socket[i], &rfds);
int retval = select(FD_SETSIZE, &rfds, NULL, NULL, &timevalue);

if(retval > 0)
{
if (FD_ISSET(s_Socket,&rfds))
{
a_Socket = accept(s_Socket, (struct sockaddr *)&fsin, &alen);
memset(clientIp,0,sizeof(clientIp));
strcpy(clientIp,inet_ntoa(fsin.sin_addr));
}
}

7. 指定客户端连接
void CTcpServerListenThread::AccepClientIpConnect(char *recIp,int sockfd,int listenport)
{
int i,j;
int ReNo=0;
CTcpServerRxTxThreadPtr ptrTcp;
if (m_TcpServerRxTxQueue.size()<=0)
return;
vector<CTcpServerRxTxThreadPtr>::iterator it;
for (it = m_TcpServerRxTxQueue.begin();it!=m_TcpServerRxTxQueue.end();it++)
{
ptrTcp = *it;
if(m_ProtocolId==ptrTcp->m_ptrCFesChan->m_Param.ProtocolId)
{
for (i = 0; i < CN_FesMaxNetRouteNum; i++)
{
if((strcmp(recIp,ptrTcp->m_ptrCFesChan->m_Param.NetRoute[i].NetDesc)==0)&&
(listenport==ptrTcp->m_ptrCFesChan->m_Param.NetRoute[i].PortNo))
{
ReNo = CheckChanNo(ptrTcp->m_ptrCFesChan->m_Param.ChanNo);
if(ReNo!=SOCKET_ERROR)
{
if (m_AcceptSocket[ReNo]!=SOCKET_ERROR) //该客户已经有老的连接
{
LOGINFO("The %s already connect! 第 %d 个ip已经连接过 m_AcceptSocket[%d] = %d\n",recIp,i,ReNo,m_AcceptSocket[ReNo]);
TcpClose(m_AcceptSocket[ReNo]);
// SetDiscNetEvent(ptrTcp->m_ptrCFesChan);
m_AcceptSocket[ReNo] = INVALID_SOCKET;
m_AcceptSocket[ReNo] = sockfd;
RecordNetEvent(m_AcceptSocket[ReNo],ptrTcp->m_ptrCFesChan);
return;
}
else//该客户还没有建立连接
{
m_AcceptSocket[ReNo] = sockfd; RecordNetEvent(m_AcceptSocket[ReNo],ptrTcp->m_ptrCFesChan);
return;
}
}
else
{
LOGINFO("所连接的客户端个数已经满了!");
return;
}
}
}
}
}
LOGINFO("不是所配置的客户端IP!");
TcpClose(sockfd);
}

8. send和recv调用

2) 先检测套接描述符是否可读、可写
fd_set fdwSet; //存储描述字集合
fd_set fdrSet;
struct timeval tv;//时间结构体
/*判断soket是否可写*/
FD_ZERO(&fdrSet); //清除集合
FD_SET(m_Socket, &fdrSet); //加入要监听的描述符
FD_ZERO(&fdwSet);
FD_SET(m_Socket, &fdwSet);
tv.tv_sec = 0;
tv.tv_usec = 10 * 1000;

len = select(m_Socket + 1, &fdrSet, &fdwSet, 0, &tv);//监测读、写的描述符
if (len > 0)
{
if (FD_ISSET(m_Socket, &fdwSet)) //可写
{
rtn = ::send(m_Socket , (char *)buff , len , 0);
}
if (FD_ISSET(m_Socket, &fdrSet)) //可读
{
len = ::recv(m_Socket , (char *)buff , 8000 , 0);
}
}
epoll与select比较
select的三大缺点:
1.每次调用select,都需要把fd集合从用户态拷贝到内核态,开销在fd很多时候很大
2.同时每次调用select都需要在内核遍历传进来的所有fd,开销在fd很多时候很大
3.select支持的文件描述数量太小了,默认1024

epoll优点:
epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

  1.对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

  2.对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。

  3.对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

总结:
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
Socket头文件
1.Linux平台:
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
2.Windows平台:
#include <Winsock.h>
#include <Windows.h>
/**
* @brief CTcpServerListenThread::InitWinsock
*
* Windows系统下网络库初始化,调用此接口才能在windows下正常使用
* @return
*/
bool CTcpServerListenThread::InitWinsock()
{
#ifdef WIN32
WSADATA wsdata;
WORD wVersion = MAKEWORD(2, 2);
if ((WSAStartup(wVersion, &wsdata)) != 0)
{
LOGERROR("WSAStartup() ERROR, errno:%d ", WSAGetLastError());
return false;
}
return true;
#else
return true;
#endif
}
Win32 UDP-Server
1.lib库及头文件
#include <WINSOCK2.H> 或 #include <Winsock.h>
#pragma comment(lib,"WS2_32.lib")
#define BUF_SIZE 1024
2.初始化套接字动态库
WSADATA wsd;
WORD wVersion = MAKEWORD(2, 2);
// 初始化套接字动态库
if (WSAStartup(wVersion, &wsd) != 0)
{
cout<<"WSAStartup failed !"<<endl;
return 1;
}
3.创建socket和bind定端口、IP
SOCKET socketSrv = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
char buf[BUF_SIZE];
int len = sizeof(SOCKADDR_IN);

AF_INET:协议域,代表IPV4
SOCK_DGRAM:专门用于局域网,基于UDP,基于广播
IPPROTO_UDP:UDP协议,可选可不选

// 设置服务器地址
ZeroMemory(buf, BUF_SIZE); //就是memset()函数的封装
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //允许接收任意IP客户端的数据
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(5000);


// 绑定套接字
nRet = bind(socketSrv,(SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (SOCKET_ERROR == nRet)
{
printf("bind failed !\n");
closesocket(socketSrv);
WSACleanup();
return -1;
}
4.设置套接字为非阻塞模式
u_long argp = 1;
if (ioctlsocket(socketSrv, FIONBIO, &argp) == SOCKET_ERROR)
{
printf("设置套接字为非阻塞模式失败!\n");
return -1;
}

5.recvfrom和sendto的收发
// 从客户端接收数据
ZeroMemory(buf, BUF_SIZE);
nRet = recvfrom(socketSrv, buf, BUF_SIZE, 0, (SOCKADDR *)&addrSrv, &len);
// 向客户端发送数据
sendto(socketSrv, "UDP Hello World !", sizeof("UDP Hello World !"), 0, (SOCKADDR *)&addrSrv, len);

函数原型:
int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
sendto(),是把UDP数据报发给指定地址;recvfrom()是从指定地址接收UDP数据报。
参数说明
?\s: socket描述符。
?\buf: UDP数据报缓存地址。
?\len: UDP数据报长度。
?\flags: 该参数一般为0。
?\to: sendto()函数参数,struct sockaddr_in类型,指明UDP数据发往哪里报。
?\tolen: 对方地址长度,一般为:sizeof(struct sockaddr_in)。
?\fromlen:recvfrom()函数参数,struct sockaddr_in类型,指明从哪里接收UDP数据报。
Win32 UDP-Client
1.lib库及头文件
#include <WINSOCK2.H> 或 #include <Winsock.h>
#pragma comment(lib,"WS2_32.lib")
#define BUF_SIZE 1024
2.初始化套接字动态库
WSADATA wsd;
WORD wVersion = MAKEWORD(2, 2);
// 初始化套接字动态库
if (WSAStartup(wVersion, &wsd) != 0)
{
cout<<"WSAStartup failed !"<<endl;
return 1;
}
3.创建socket和设置服务器地址
char buf[BUF_SIZE]; // 接受数据
SOCKADDR_IN servAddr; // 服务器套接字地址
OCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);
int nRet;

// 设置服务器地址
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
servAddr.sin_port = htons(5000);

4. recvfrom和sendto的收发
// 向服务器发送数据
int nServAddLen = sizeof(servAddr);
sendto(sockClient, buf, BUF_SIZE, 0, (sockaddr *)&servAddr, nServAddLen);
nRet = recvfrom(sockClient, buf, BUF_SIZE, 0, (sockaddr *)&servAddr, &nServAddLen);

Linux UDP-Server
案例:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define UDP_TEST_PORT 50001

int main(int argC, char* arg[])
{

struct sockaddr_in addr;
int sockfd, len = 0;
int addr_len = sizeof(struct sockaddr_in);
char buffer[256];
/* 建立socket,注意必须是SOCK_DGRAM */
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket");
exit(1);
}
/* 填写sockaddr_in 结构 */
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(UDP_TEST_PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);// 接收任意IP发来的数据
/* 绑定socket */
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("connect");
exit(1);
}
while (1)
{
bzero(buffer, sizeof(buffer));
len = recvfrom(sockfd, buffer, sizeof(buffer), 0,(struct sockaddr *)&addr, &addr_len);
/* 显示client端的网络地址和收到的字符串消息 */
printf("Received a string from client %s, string is: %s\n",
inet_ntoa(addr.sin_addr), buffer);
/* 将收到的字符串消息返回给client端 */
sendto(sockfd, buffer, len, 0, (struct sockaddr *)&addr, addr_len);

}
return 0;
}
Linux UDP-Client
案例:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define UDP_TEST_PORT 50001
#define UDP_SERVER_IP "127.0.0.1"

int main(int argC, char* arg[])
{
struct sockaddr_in addr;
int sockfd, len = 0;
int addr_len = sizeof(struct sockaddr_in);
char buffer[256];
/* 建立socket,注意必须是SOCK_DGRAM */
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket");
exit(1);
}
/* 填写sockaddr_in*/
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(UDP_TEST_PORT);
addr.sin_addr.s_addr = inet_addr(UDP_SERVER_IP);
while (1)
{
bzero(buffer, sizeof(buffer));
printf("Please enter a string to send to server: \n");
/* 从标准输入设备取得字符串*/
len = read(STDIN_FILENO, buffer, sizeof(buffer));
/* 将字符串传送给server端*/
sendto(sockfd, buffer, len, 0, (struct sockaddr *)&addr, addr_len);
/* 接收server端返回的字符串*/
len = recvfrom(sockfd, buffer, sizeof(buffer), 0,(struct sockaddr *)&addr, &addr_len);
printf("Receive from server: %s\n", buffer);
}
return 0;
}

posted on 2019-09-25 08:43  码农er  阅读(316)  评论(0编辑  收藏  举报