• 软件:1160
  • 资讯:41601|
  • 收录网站:97880|

IT精英团

tcp和udp的基本函数调用过程以及如何选择

tcp和udp的基本函数调用过程以及如何选择

浏览次数:
评论次数:
编辑: 阳煦
信息来源: 51CTO博客
更新日期: 2021-06-11 01:10:18
摘要

tcp和udp的基本函数调用过程及如何选择,1.socket编程1.1概述TCP是TCP/IP体系中面向连接的传输层协议,它提供全双工和可靠交付的服务。它采用许多机制来确保端到端结点之间的可靠数据传输,如采用序列号、确认重传、滑动窗口等。首先,TCP要为所发送的每一个报文段加上序列号,保证每一个报文段能被接收方接收,并只被正确的接收一次。其次,TCP采用具有重传功能的积极确认技术作为可靠数据流传输服务的基础。这里“确认”是指接收...

  • 资讯详情

1. socket编程

1.1 概述

TCP是TCP/IP架构中面向连接的传输层协议,提供全双工、可靠的传递服务。它采用序列号、重传确认、滑动窗口等多种机制来保证端到端节点之间可靠的数据传输。

首先,TCP要给发送的每个报文段加上一个序列号,以保证每个报文段都能被接收方接收到,并且只能正确接收一次。

其次,TCP采用具有重传功能的肯定应答技术作为可靠的数据流传输服务的基础。这里的“确认”是指接收端正确接收到报文段后,向发送端发回确认(ack)报文。发送方将每个发送的消息段备份到自己的缓冲区中,在收到相应的确认之前不会丢弃保存的消息段。“肯定”是指发送方在发送每个报文段的同时启动一个定时器,如果添加定时器的定时到期,且尚未到达关于该报文段的确认信息,则发送方认为该报文段已经丢失,主动重发。为了避免因网络延迟而导致的延迟确认和重复确认,TCP规定确认信息中应携带一个报文段的序列号,以便接收方能够正确地将报文段与确认链接起来。

最后,可变长度滑动窗口协议用于流量控制,以防止因发送方和接收方不匹配而导致的数据丢失。这里使用的滑动窗口协议的工作原理与数据链路层的工作原理完全相同。唯一不同的是传输层使用滑动窗口协议实现端到端节点之间的流量控制,而数据链路层用于实现相邻节点之间的流量控制。TCP采用变长滑动窗口,使发送方和接收方根据自身CPU和数据缓存资源动态调整数据发送和接收能力,从而使其更加灵活合理。

1.2 tcp服务端编程

1.2.1 TCP通信的基本步骤

服务器:socket-bind-listen-while(1){-accept-recv-send-close-}-close

客户端:socket-connect-send-recv-close

1.2.2 服务器端头文件包含

# include sys/type . h

#包含sys/socket.h

#包含netinet/in.h

#包括arpa/inet.h

#包含unistd.h

#include string.h

#包含stdio.h

#包含stdlib.h

1.2.3 socket函数

函数:生成一个windows套接字描述符

原型:int socket (int domain,int type,int protocol);

参数:

域{af _ inet: IPv4网络协议af _ inet6: IPv6网络协议}

{ TCP:SOCK _ STREAM UDP:SOCK _ DGRAM }类型

协议指定套接字使用的传输协议号。常用的协议有:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。它们分别对应于TCP、UDP、STCP和TIPC协议。当协议默认为0时,根据类型参数的值自动选择协议类型。

返回值:如果成功则返回windows套接字描述符,如果失败则返回-1。

常见示例:

int sfd=socket(AF_INET,SOCK_STREAM,0);

if(sfd==-1)

{

perror(' socket ');

出口(-1);

}

1.2.4 bind函数

功能:用于绑定端口号和IP地址,使windows套接字与指定的端口号和IP地址相关联。

原型:int bind (int sockfd,struct sockaddr * my _ addr,int addr len);

参数:

Sockfd是前一个套接字的返回值

addrlen sockaddr的结构长度。通常是计算出的sizeof(struct sockaddr);

My_addr是结构指针变量

为不同的套接字域定义了通用数据结构:

//这种结构不常用

想要的是

{

无符号短int sa _ family//调用socket()时的域参数,即AF_INET值。

char sa _ data[14];//最多使用14个字符

};

//这个sockaddr结构由于socketdomains不同,结构定义也不同。例如,如果使用AF_INET域,其socketaddr结构定义为:

结构sockaddr _ in//公共结构

{

无符号短int sin _ family//is sa _ famile

y AF_INET uint16_t sin_port; //为使用的port编号 struct in_addr sin_addr; //为IP 地址 unsigned char sin_zero[8]; //未使用 }; struct in_addr { uint32_t s_addr; };

返回值:成功则返回0,失败返回-1,并设置errno,最常见的errno有以下两种:

  • EACCES,被绑定的地址是受保护的地址,仅超级用户能够访问,比如如果绑定在1-1023端口的时候,就会报该错误。
  • EADDRINUSE,被绑定的地址正在使用中,比如将socket绑定在一个处于TIME_WAIT状态的socket地址。

常用实例:

struct sockaddr_in my_addr;  //定义结构体变量
memset(&my_addr, 0, sizeof(struct sockaddr)); //将结构体清空
//或bzero(&my_addr, sizeof(struct sockaddr));
my_addr.sin_family = AF_INET;  //表示采用Ipv4网络协议
my_addr.sin_port = htons(8888);  //表示端口号为8888,通常是大于1024的一个值。
//htons()用来将参数指定的16位hostshort转换成网络字符顺序
my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); // //inet_addr()用来将IP地址字符串转换成网络所使用的二进制数字,如果为INADDR_ANY,这表示服务器自动填充本机IP地址。
if(bind(sfd, (struct sockaddr*)&my_str, sizeof(struct socketaddr)) == -1)
    {perror("bind");close(sfd);exit(-1);}
/*通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址*/

注意:如果my_addr.sin_addr.s_addr = htonl(INADDR_ANY)时,INADDR_ANY就是指定地址为0.0.0.0的地址,它其实是表示不确定地址,一般是用于多网卡的机器上,也就是有多个IP地址,如果设置了INADDR_ANY,那么根据端口号,无论连接哪个IP地址,都是可以连接成功的。

1.2.5 listen函数

功能:使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。如果客户端有连接请求,端口就会接受这个连接。
原型:int listen(int sockfd, int backlog);
参数:
sockfd为前面socket的返回值.即sfd
backlog指定同时能处理的最大连接要求,通常为10或者5。 最大值可设至128
返回值:成功则返回0,失败返回-1
常用实例:

if(listen(sfd, 10) == -1)
{
    perror("listen");
    close(sfd);
    exit(-1);
}

1.2.6 accept函数

功能:接受远程计算机的连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求。当accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以继续使用,继续监听其它客户机的连接请求。
原型:int accept(int s,struct sockaddr * addr,int * addrlen);
参数:
s为前面socket的返回值,即sfd
addr为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。
addrlen表示结构体的长度,为整型指针
返回值:成功则返回新的文件描述符new_fd,失败返回-1
常用实例:

struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(struct sockaddr));
int addrlen = sizeof(struct sockaddr);
int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);
if(new_fd == -1)
{
    perror("accept");
    close(sfd);
    exit(-1);
}
printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

注意:accept函数只是从监听队列中取出连接,而不论连接处于什么状态,网络状况有什么变化。

1.2.7 recv函数

功能:接收远端主机传来的数据,并把数据存到由参数buf 指向的内存空间
原型:int recv(int sockfd,void *buf,int len,unsigned int flags);
参数:sockfd为前面accept的返回值.即new_fd,也就是新的套接字。
buf表示缓冲区
len表示缓冲区的长度
flags通常为0
返回值:
>0 成功,返回实际接收到的字符数;
=0 连接关闭,说明recv在等待接收数据时网络中断了;
-1 表示出错;
常用实例:

char buf[512] = {0};
if(recv(new_fd, buf, sizeof(buf), 0) == -1)
{
    perror("recv");
    close(new_fd);
    close(sfd);
    exit(-1);
}
puts(buf);

注意:read函数返回值不一样,大于0 是返回字节数,等于0是读到文件末尾了,小于0则表示出现了错误,如果错误为EINTR说明是由中断引起的,如果是ECONNNERST则表示网络连接出现了问题。

1.2.8 send函数

功能:发送数据给指定的远端主机
原型:int send(int s,const void * msg,int len,unsigned int flags);
参数:s为前面accept的返回值.即new_fd
msg一般为常量字符串
len表示长度
flags通常为0
返回值:
>0 成功,返回实际传送出去的字符数,可能会少于你所指定的发送长度;
=0 连接关闭,网络中断了;
-1 表示出错;
常用实例:

if(send(new_fd, "hello", 6, 0) == -1)
{
    perror("send");
    close(new_fd);
    close(sfd);
    exit(-1);
}

注意:write函数返回小于0时,如果为EPIPE,则表示网络连接出现了问题。

1.2.9 close函数

功能:当使用完文件后若已不再需要则可使用close()关闭该文件,并且close()会让数据写回磁盘,并释放该文件所占用的资源
原型:int close(int fd);
参数:fd为前面的sfd,new_fd
返回值:若文件顺利关闭则返回0,发生错误时返回-1
常用实例:close(new_fd);
close(sfd);
注意:在多进程并发服务器中,父子进程共享套接字,有多少个进程共享该套接字,该套接字就有多少个引用计数,此时其中一个进程调用close只是关闭了当前进程的这个文件描述符,但并没有发生四次挥手,直到这个套接字的引用计数为0时,才会发生四次挥手

1.2.10 sockatmark函数

功能:判断sockfd是否处于带外标记,即判断下一个读取的数据是否含有带外数据,若含有,则调用带MSG_OOB标志的recv来读取带外数据
原型:int sockatmark(int sockfd);
参数:fd为前面的sfd,new_fd
返回值:返回1则说明下一个数据时带外数据,若返回0,则不是

1.3 tcp客户端编程

1.3.1 connect函数

功能:用来请求连接远程服务器,将参数sockfd 的socket 连至参数serv_addr 指定的服务器IP和端口号上去。
原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
参数:sockfdà为前面socket的返回值,即sfd
serv_addrà为结构体指针变量,存储着远程服务器的IP与端口号信息。
addrlenà表示结构体变量的长度
返回值:成功则返回0,失败返回-1
常用实例:

struct sockaddr_in seraddr;//请求连接服务器
memset(&seraddr, 0, sizeof(struct sockaddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8888); //服务器的端口号
seraddr.sin_addr.s_addr = inet_addr("192.168.0.101");  //服务器的ip
if(connect(sfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr)) == -1)
{perror("connect");close(sfd);exit(-1);}

将上面的头文件以及各个函数中的代码全部拷贝就可以形成一个完整的例子,此处省略。
还可以不写客户端程序,使用telnet远程登录来检测我们的服务器端程序。比如我们的服务器程序在监听8888端口,我们可以用telnet 192.168.0.101 8888来查看服务端的状况。

2. tcp编程实现

2.1 使用类封装tcp的基本操作

//头文件 SxTcp.h
#ifndef __SXTCP_H__
#define __SXTCP_H__
#include <stdio.h>
#define TIMEOUT_SEC 1
#define MAX_READ_SIZE BUFSIZ
#define DEFAULT_EPOLL_FD_NUM 1024
//Tcp类
class CTcp
{
//构造函数
public:
    CTcp ();
    CTcp (int nSock);
    virtual ~CTcp ();
//重载操作符
public:
    int operator = (int);//赋值
    int operator != (int) const;//不等于操作符
    int operator == (int) const;//等于操作符
//公有成员函数
public:
    int GetHandle () const;//取出m_nSock
    int Open ();//创建socket
    int Close ();//关闭监听socket
    int Connect (const char *, int) const;//连接(未设置超时)
    int Bind (const char *, int) const;//绑定
    int Listen (int nNum) const;//监听
    int Accept () const;//接受连接
    int Recv(int nFd, char* buf, int nBufLen);//服务端接收
    int Send(int nFd, char* buf, int nBufLen);//服务端发送
    void Close (int nFd);//服务端关闭连接socket
    int Send (const void *, int, int = TCP_TIMEOUT) const;//客户端发送数据
    int Recv (void *, int, int = TCP_TIMEOUT) const;//客户端接收数据
    static const int SOCK_ERROR;
    static const int SOCK_TIMEOUT;
    static const long TCP_TIMEOUT;
//私有成员变量
private:
    int m_nSock;
};
#endif
//sxTcp.cpp
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdarg.h>
#include <arpa/inet.h>
#include <iostream>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <assert.h>
#include <sys/epoll.h>
#include <signal.h>
#include "SxTcp.h"
const int CTcp::SOCK_ERROR = -100;
const int CTcp::SOCK_TIMEOUT = -101;
const long CTcp::TCP_TIMEOUT = 500000;
//构造函数
CTcp::CTcp ()
{
    m_nSock = -1;
}
//构造函数
CTcp::CTcp (int p_iSock)
{
    m_nSock = p_iSock;
}
//析构函数
CTcp::~CTcp ()
{
    Close ();
}
/*赋值
  入参:nSockfd - socket句柄
  出参:赋值后的socket句柄
*/
int CTcp::operator = (int p_iSockfd)
{
    //isfdtype判断nSockfd是否为指定类型,S_IFSOCK判断是否为套接字描述符,返回1是,0不是,-1出错
    assert ((m_nSock == -1) && (p_iSockfd > -1) && (isfdtype (p_iSockfd, S_IFSOCK) == 1));
    m_nSock = p_iSockfd;
    return m_nSock;
}
/*判断两个socket句柄是否不相等
  入参:n - "!="右边的socket句柄
  出参:1:不相等;0:相等
*/
int CTcp::operator != (int p_iSockfd) const
{
    return (m_nSock != p_iSockfd);
}
/*判断两个socket句柄是否相等
  入参:n - "=="右边的socket句柄
  出参:1:相等;0:不相等
*/
int CTcp::operator == (int p_iSockfd) const
{
    return (m_nSock == p_iSockfd);
}
/*取出socket句柄
  入参:无
  出参:取出的socket句柄
*/
int CTcp::GetHandle () const
{
    return m_nSock;
}
/*创建socket
  入参:无
  出参:1: 成功 ; 0: 失败
*/
int CTcp::Open ()
{
    assert (m_nSock == -1);
    //获取tcp套接字
    m_nSock = socket (AF_INET, SOCK_STREAM, 0);
    return (m_nSock != -1);
}
/*关闭socket
  入参:无
  出参:1: 成功 ; 0: 失败
*/
int CTcp::Close ()
{
    if (m_nSock != -1)
    {
        close (m_nSock);
        m_nSock = -1;
    }
    return 1;
}
/*连接(未设置超时),默认为阻塞IO
  入参:pHost - IP地址或主机名
    nPort - 端口
  出参:1: 成功 ; 0: 失败
*/
int CTcp::Connect (const char *p_szHost, int p_iPort) const
{
    assert ((m_nSock != -1) && p_szHost && (p_iPort > 0));
    struct sockaddr_in addr;
    struct hostent *phe = NULL;
    memset ((void*)&addr, 0, sizeof (addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons (p_iPort);
    if ((addr.sin_addr.s_addr = inet_addr (p_szHost)) == -1)
    {
        if ((phe = gethostbyname (p_szHost)) == NULL)
            return 0;
        memcpy ((char *)&addr.sin_addr, phe->h_addr, phe->h_length);
    }
    return (connect (m_nSock, (struct sockaddr *)&addr, sizeof (addr)) == 0);
}
/*绑定
  入参:pIP - IP地址
    nPort - 端口
  出参:1: 成功 ; 0: 失败
*/
int CTcp::Bind (const char *pIP, int nPort) const
{
    assert ((m_nSock != -1) && (nPort > 0));
    struct sockaddr_in addr;
    struct hostent *phe = NULL;
    int opt=1;
    if (setsockopt (m_nSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) == -1)
    {
        return 0;
    }
    memset (&addr, 0, sizeof (addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons (nPort);
    if (!pIP)
    {
        addr.sin_addr.s_addr = htonl (INADDR_ANY);
    }
    else
    {
        if ((addr.sin_addr.s_addr = inet_addr (pIP)) == -1)
        {
            if ((phe = gethostbyname (pIP)) == NULL)
                return 0;
            memcpy ((char *)&addr.sin_addr, phe->h_addr, phe->h_length);
        }
    }
    return (bind (m_nSock, (struct sockaddr *)&addr, sizeof (addr)) == 0);
}
/*监听
  入参:nNum - 监听数目
  出参:1: 成功 ; 0: 失败
*/
int CTcp::Listen (int nNum) const
{
    assert ((m_nSock != -1) && (nNum > 0));
    return (listen (m_nSock, nNum) == 0);
}
/*接受连接
  入参:无
  出参:其他: 连接套接字句柄 ; -1: 失败
*/
int CTcp::Accept () const
{
    assert (m_nSock != -1);
    return (accept (m_nSock, (struct sockaddr *)NULL, NULL));
}
/*服务端接收数据
  入参:pBuf - 接收缓存
    nCount - 需接收字节数
  出参:实际接收字节数 ,如果接收失败,返回负数,如果对方关闭,返回0
*/
int CTcp::Recv(int nFd, char* buf, int nBufLen)
{
    assert(nFd != -1 );
    return recv(nFd, buf, nBufLen, 0);
}
/*客户端接收数据
  入参:pBuf - 接收缓存
    nCount - 需接收字节数
    nMicsec - socket超时值,单位:微妙,缺省:500000微妙
  出参:实际接收字节数 ,如果接收失败,返回负数,如果对方关闭,返回0
*/
int CTcp::Recv (void *pBuf, int nCount, int nMicsec) const
{
    assert ((m_nSock != -1) && pBuf && (nCount > 0));
    int sn = 0, rn = 0;
    struct timeval tvlTime;
    fd_set rdfdset;
    if (nMicsec >= 0)
    {
        tvlTime.tv_sec = nMicsec / 1000000;
        tvlTime.tv_usec = abs (nMicsec - tvlTime.tv_sec * 1000000);
    }
    FD_ZERO (&rdfdset);
    FD_SET (m_nSock, &rdfdset);
    if (nMicsec > 0)
        sn = select (m_nSock + 1, &rdfdset, NULL, NULL, &tvlTime);
    else
        sn = select (m_nSock + 1, &rdfdset, NULL, NULL, NULL);
    switch (sn)
    {
    case -1:
        return SOCK_ERROR;
    case 0:
        return SOCK_TIMEOUT;
    }
    if ((rn = read (m_nSock, pBuf, nCount)) < 0)
        return SOCK_ERROR;
    return rn;
}
/*服务端发送数据
入参:pBuf - 发送缓存
nCount - 需发送字节数
出参:实际发送字节数 ,如果发送失败,返回负数
*/
int CTcp::Send(int nFd, char* buf, int nBufLen)
{
    assert(nFd != -1 );
    return send(nFd, buf, nBufLen, 0);
}
/*客户端发送数据
  入参:pBuf - 发送缓存
    nCount - 需发送字节数
    nMicsec - socket超时值,单位:微妙,缺省:500000微妙
  出参:实际发送字节数 ,如果发送失败,返回负数
*/
int CTcp::Send (const void *pBuf, int nCount, int nMicsec) const
{
    assert ((m_nSock != -1) && pBuf && (nCount > 0));
    int sn = 0, wn = 0;
    struct timeval tvlTime;
    fd_set wtfdset;
    if (nMicsec >= 0)
    {
        tvlTime.tv_sec = nMicsec / 1000000;
        tvlTime.tv_usec = abs (nMicsec - tvlTime.tv_sec * 1000000);
    }
    FD_ZERO (&wtfdset);
    FD_SET (m_nSock, &wtfdset);
    if (nMicsec >= 0)
        sn = select (m_nSock + 1, NULL, &wtfdset, NULL, &tvlTime);
    else
        sn = select (m_nSock + 1, NULL, &wtfdset, NULL, NULL);
    switch (sn)
    {
    case -1:
        return SOCK_ERROR;
    case 0:
        return SOCK_TIMEOUT;
    }
    if ((wn = send (m_nSock, pBuf, nCount, 0)) <= 0)
        return SOCK_ERROR;
    return wn;
}
void CTcp::Close (int nFd)
{
    if (nFd != -1 )
    {
        close(nFd);
        nFd = -1;
    }
}

将该类编译成动态库:

g++ -g -c SxTcp.cpp -fPIC
g++ -g -o libnetwork.so SxTcp.o -shared

2.2 利用类CTcp实现基本服务端和客户端

//服务端TestServer.cpp

//TestServer.cpp
#include <stdio.h>
#include <string.h>
#include "../../network/SxTcp.h"
int main()
{
    CTcp tcp;
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }
    iRet = tcp.Bind("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket bind failed");
        return -1;
    }
    iRet = tcp.Listen(10);
    if (iRet == 0)
    {
        perror("socket listen failed");
        return -1;
    }
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        iFd = tcp.Accept();
        if (iFd == -1 )
        {
            perror("socket accept failed");
            return -1;
        }
        iRet = tcp.Recv(iFd, buf, sizeof(buf));
        if (iRet < 0 )
        {
            perror("recv failed");
            tcp.Close(iFd);
            return -1;
        }
        else if(iRet == 0)
        {
            perror("socket not connect");
            tcp.Close(iFd);
            return -1;
        }
        fprintf(stdout, "recv data is:%s\n", buf);
        memset(buf, 0, sizeof(buf));
        strncpy(buf, "I have redv your data,over!", sizeof(buf)-1);
       
        iRet = tcp.Send(iFd, buf, strlen(buf));
        if (iRet < 0)
        {
            perror("send failed");
            tcp.Close(iFd);
            return -1;
        }
        else if(iRet == 0)
        {
            perror("socket not connect");
            tcp.Close(iFd);
            return -1;
        }
    }
    return 0;
}

//客户端

//TestClient.cpp
#include <stdio.h>
#include <iostream>
#include <string.h>
#include "../../network/SxTcp.h"
using namespace std;
int main()
{
    CTcp tcp;
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }
    iRet = tcp.Connect("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket connect failed");
        return -1;
    }
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        cout << "please input some string:";
        cin >> buf;
        iRet = tcp.Send(buf, strlen(buf));
        if (iRet < 0 && errno != EAGAIN)
        {
            perror("send failed");
            return -1;
        }
        else if(iRet == 0)
        {
            perror("connect is closed");
            return -1;
        }
        memset(buf, 0, sizeof(buf));
        iRet = tcp.Recv(buf, sizeof(buf));
        if (iRet < 0 && errno != EAGAIN)
        {
            perror("recv failed");
            return -1;
        }
        else if(iRet == 0)
        {
            perror("socket not connect");
            return -1;
        }
        fprintf(stdout, "recv data is:%s\n", buf);
    }
    return 0;
}

分别编译服务端和客户端,然后发送数据,会发现客户端发送完第一次后,再第二次循环中会报recv failed。
这是因为服务端阻塞在accept了,没办法第二次接收和发送数据,那么客户端超时以后就会报错,返回负数,导致客户端退出。
当然也可以在服务端的recv和send外再加一个循环,如下:

//TestServer.cpp
#include <stdio.h>
#include <string.h>
#include "../../network/SxTcp.h"
int main()
{
    CTcp tcp;
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }
    iRet = tcp.Bind("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket bind failed");
        return -1;
    }
    iRet = tcp.Listen(10);
    if (iRet == 0)
    {
        perror("socket listen failed");
        return -1;
    }
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        iFd = tcp.Accept();
        if (iFd == -1 )
        {
            perror("socket accept failed");
            return -1;
        }
        while(1){
            memset(buf, 0, sizeof(buf));
            iRet = tcp.Recv(iFd, buf, sizeof(buf));
            if (iRet < 0 )
            {
                perror("recv failed");
                tcp.Close(iFd);
                return -1;
            }
            else if(iRet == 0)
            {
                perror("socket not connect");
                tcp.Close(iFd);
                return -1;
            }
            fprintf(stdout, "recv data is:%s\n", buf);
            memset(buf, 0, sizeof(buf));
            strncpy(buf, "I have redv your data,over!", sizeof(buf)-1);
           
            iRet = tcp.Send(iFd, buf, strlen(buf));
            if (iRet < 0)
            {
                perror("send failed");
                tcp.Close(iFd);
                return -1;
            }
            else if(iRet == 0)
            {
                perror("socket not connect");
                tcp.Close(iFd);
                return -1;
            }
        }
    }
    return 0;
}

但很显然,这样就没办法接收到第二个连接了,那么怎么解决呢?往下面看。

3. 网络编程模式

上面的虽然可以实现多个客户端访问,但是仍然是阻塞模式(即一个客户访问的时候会阻塞不让另外的客户访问)。解决办法有三种,分别是多进程、多线程、异步IO。

3.1 多进程

//因为开销比较大,所以不常用

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "../../network/SxTcp.h"
int main()
{
    CTcp tcp;
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }
    iRet = tcp.Bind("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket bind failed");
        return -1;
    }
    iRet = tcp.Listen(10);
    if (iRet == 0)
    {
        perror("socket listen failed");
        return -1;
    }
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        iFd = tcp.Accept();
        if (iFd == -1 )
        {
            perror("socket accept failed");
            return -1;
        }
        if (fork() == 0)
        {
            while(1){
                memset(buf, 0, sizeof(buf));
                iRet = tcp.Recv(iFd, buf, sizeof(buf));
                if (iRet < 0 )
                {
                    perror("recv failed");
                    tcp.Close(iFd);
                    return -1;
                }
                else if(iRet == 0)
                {
                    perror("socket not connect");
                    tcp.Close(iFd);
                    return -1;
                }
                fprintf(stdout, "recv data is:%s\n", buf);
                memset(buf, 0, sizeof(buf));
                strncpy(buf, "I have redv your data,over!", sizeof(buf)-1);
               
                iRet = tcp.Send(iFd, buf, strlen(buf));
                if (iRet < 0)
                {
                    perror("send failed");
                    tcp.Close(iFd);
                    return -1;
                }
                else if(iRet == 0)
                {
                    perror("socket not connect");
                    tcp.Close(iFd);
                    return -1;
                }
            }
        }
        else
        {
            tcp.Close(iFd);
        }
    }
    return 0;
}

3.2 多线程

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <thread>
#include "../../network/SxTcp.h"
CTcp tcp;
void ReadThread(void* arg)
{
    int *pFd = (int*)arg;
    int iFd = *pFd;
    char buf[128] = {0};
    int iRet = 0;
    while(1){
                memset(buf, 0, sizeof(buf));
                iRet = tcp.Recv(iFd, buf, sizeof(buf));
                if (iRet < 0 )
                {
                    perror("recv failed");
                    tcp.Close(iFd);
                    break;
                }
                else if(iRet == 0)
                {
                    perror("socket not connect");
                    tcp.Close(iFd);
                    break;
                }
                fprintf(stdout, "recv data is:%s\n", buf);
                memset(buf, 0, sizeof(buf));
                strncpy(buf, "I have redv your data,over!", sizeof(buf)-1);
               
                iRet = tcp.Send(iFd, buf, strlen(buf));
                if (iRet < 0)
                {
                    perror("send failed");
                    tcp.Close(iFd);
                    break;
                }
                else if(iRet == 0)
                {
                    perror("socket not connect");
                    tcp.Close(iFd);
                    break;
                }
    }
           
}
int main()
{
    int iRet = 0;
    int iFd = 0;
    char buf[128] = {0};
   
    iRet = tcp.Open();
    if (iRet == 0)
    {
        perror("socket create failed");
        return -1;
    }
    iRet = tcp.Bind("192.168.233.250", 6666);
    if (iRet == 0)
    {
        perror("socket bind failed");
        return -1;
    }
    iRet = tcp.Listen(10);
    if (iRet == 0)
    {
        perror("socket listen failed");
        return -1;
    }
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        iFd = tcp.Accept();
        if (iFd == -1 )
        {
            perror("socket accept failed");
            return -1;
        }
        std::thread t_read(ReadThread, (void*)&iFd);
        t_read.detach();
    }
    return 0;
}

3.3 异步IO

异步其实就是epoll和select模式,可以看另外的两篇专门讲epoll和select的文章。

4. 使用UDP编程

4.1 UDP协议

4.1.1 概述

UDP即用户数据报协议,它是一种无连接协议,因此不需要像TCP那样通过三次握手来建立一个连接。同时,一个UDP应用可同时作为应用的客户或服务器方。由于UDP协议并不需要建立一个明确的连接,因此建立UDP应用要比建立TCP应用简单得多。

它比TCP协议更为高效,也能更好地解决实时性的问题。如今,包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都使用UDP协议。

4.1.2 Udp数据包头格式

源端口占用16bit,表示应用程序通过哪个端口来发送数据包;
目的端口占用16bit,表示数据包发送给对方应用程序的哪个端口;
长度占用16bit,表示包含头部在内的udp数据包的长度;
校验占用16bit,用来检查数据包是否存在差错;

4.1.3 udp基本通信流程及函数

UDP通信流程图如下:
服务端:socket—bind—recvfrom—sendto—close
客户端:socket----------sendto—recvfrom—close
sendto()函数原型:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
recvfrom()函数原型:
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一个struct sockaddr类型的变量,该变量保存连接机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或 当出现错误时返回-1,并置相应的errno。
注意:socket编程还提供了一对函数sendmsg/recvmsg用于读写数据,该对函数既可用于tcp报文,也可用于udp报文,是通用的。

4.2 UDP编程实现

Example:

//UDP的基本操作
//服务器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket");
        exit(-1);
    }
 
    struct sockaddr_in saddr;
    bzero(&saddr, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = INADDR_ANY;
    if(bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        close(sfd);
        exit(-1);
    }
 
    char buf[512] = {0};
    while(1)
    {
        struct sockaddr_in fromaddr;
        bzero(&fromaddr, sizeof(fromaddr));
        int fromaddrlen = sizeof(struct sockaddr);
        if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
        {
            perror("recvfrom");
            close(sfd);
            exit(-1);
        }
printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
 
        sendto(sfd, "world", 6, 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
}
 
    close(sfd);
}
//客户端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket");
        exit(-1);
    }
 
    struct sockaddr_in toaddr;
    bzero(&toaddr, sizeof(toaddr));
    toaddr.sin_family = AF_INET;
    toaddr.sin_port = htons(atoi(argv[2])); //此处的端口号要跟服务器一样
    toaddr.sin_addr.s_addr = inet_addr(argv[1]); //此处为服务器的ip
    sendto(sfd, "hello", 6, 0, (struct sockaddr*)&toaddr, sizeof(struct sockaddr));
 
    char buf[512] = {0};
    struct sockaddr_in fromaddr;
    bzero(&fromaddr, sizeof(fromaddr));
    int fromaddrlen = sizeof(struct sockaddr);
    if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
    {
        perror("recvfrom");
        close(sfd);
        exit(-1);
    }
printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
 
    close(sfd);
}

Example2:

//UDP发送文件,先发文件大小,再发文件内容
//服务器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket");
        exit(-1);
    }
 
    struct sockaddr_in saddr;
    bzero(&saddr, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = INADDR_ANY;
    if(bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        close(sfd);
        exit(-1);
    }
 
    char buf[512] = {0};
    struct sockaddr_in fromaddr;
    bzero(&fromaddr, sizeof(fromaddr));
    int fromaddrlen = sizeof(struct sockaddr);
    if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
    {
        perror("recvfrom");
        close(sfd);
        exit(-1);
    }
    printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
    FILE* fp = fopen("1.txt","rb");
    struct stat st;  //用于获取文件内容的大小
    stat("1.txt", &st);
    int filelen = st.st_size;
    sendto(sfd, (void*)&filelen, sizeof(int), 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
    while(!feof(fp))   //表示没有到文件尾
    {
        int len = fread(buf,1,sizeof(buf),fp);
        sendto(sfd, buf, len, 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
}
 
    close(sfd);
}
//客户端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 512
int main(int argc, char* argv[])
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket");
        exit(-1);
    }
 
    struct sockaddr_in toaddr;
    bzero(&toaddr, sizeof(toaddr));
    toaddr.sin_family = AF_INET;
    toaddr.sin_port = htons(atoi(argv[2]));
    toaddr.sin_addr.s_addr = inet_addr(argv[1]);
    sendto(sfd, "hello", 6, 0, (struct sockaddr*)&toaddr, sizeof(struct sockaddr));
 
    char buf[BUFSIZE] = {0};
    struct sockaddr_in fromaddr;
    bzero(&fromaddr, sizeof(fromaddr));
    int fromaddrlen = sizeof(struct sockaddr);
    int filelen = 0;   //用于保存文件长度
    FILE* fp = fopen("2.txt","w+b");
//接收文件的长度
recvfrom(sfd, (void*)&filelen, sizeof(int), 0, (struct sockaddr*)&fromaddr, &fromaddrlen);
    printf("the length of file is %d\n",filelen);
    printf("Create a new file!\n");
    printf("begin to reveive file content!\n");
    //接收文件的内容
while(1)
    {
        int len = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen);
        if(len < BUFSIZE)
//如果接收的长度小于BUFSIZE,则表示最后一次接收,此时要用break退出循环
        {
            fwrite(buf,sizeof(char),len,fp);
            break;
        }
        fwrite(buf,sizeof(char),len,fp);
    }
    printf("receive file finished!\n");
    close(sfd);
}

5. 协议的选择

  • 对数据要求高可靠性的应用需选择TCP协议,如验证、密码字段的传送都是不允许出错的,而对数据的可靠性要求不那么高的应用可选择UDP传送;
  • TCP协议在传送过程中要使用三次握手、重传确认等手段来保证数据传输的可靠性。使用TCP协议会有较大的时延,因此不适合对实时性要求较高的应用,如VOIP、视频监控等。相反,UDP协议则在这些应用中能发挥很好的作用;
  • 由于TCP协议的提出主要是解决网络的可靠性问题,它通过各种机制来减少错误发生的概率。因此,在网络状况不是很好的情况下需选用TCP协议(如在广域网等情况),但是若在网络状况很好的情况下(如局域网等)就不需要再采用TCP协议,而建议选择UDP协议来减少网络负荷;
C语言基础知识——存储类
« 上一篇
返回列表
下一篇 »
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
你会是第一个来这里评论的人吗?
最近发布资讯
更多