Socket 开发记录

这几天需要用socket 开发一个UDP程序,接收远程控制指令,开发完毕后,就仔细的学习了下,在此处记录一下socket开发过程。

首先,声明一下这边文章都是拜读了几位大神的文章后,自己重新整理的。

1 socket介绍

socket编程是一门技术,它主要是在网络通信中经常用到。

既然是一门技术,由于现在是面向对象的编程,一些计算机行业的大神通过抽象的理念,在现实中通过反复的理论或者实际的推导,提出了抽象的一些通信协议,基于tcp/ip协议,提出大致的构想,一些泛型的程序大牛在这个协议的基础上,将这些抽象化的理念接口化,针对协议提出的每个理念,专门的编写制定的接口,与其协议一一对应,形成了现在的socket标准规范,然后将其接口封装成可以调用的接口,供开发者使用。

目前,开发者开发出了很多封装的类来完善socket编程,都是更加方便的实现刚开始socket通信的各个环节,所以我们首先必须了解socket的通信原理,只有从本质上理解socket的通信,才可能快速方便的理解socket的各个环节,才能从底层上真正的把握。

有很多种套接字(socket),比如 DARPA Internet 地址(Internet 套接字)、本地节点的路径名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。但我能用到的大概就只一种套接字——Internet 套接字,它是最具代表性的,也是最经典最常用的。

要理解socket必须的得理解tcp/ip,TCP/IP协议不同于OSI模型的7个分层,它是根据这7个分层,将其重新划分,TCP/IP也对OSI的网络模型层进行了划分,大致如下:
在这里插入图片描述
TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中

应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等

传输层:TCP,UDP

网络层:IP,ICMP,OSPF,EIGRP,IGMP

数据链路层:SLIP,CSLIP,PPP,MTU

每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的:
在这里插入图片描述
到目前为止,大致的了解了应用程序和tcpip协议的大致关系,我们只是知道socket编程是在tcp/IP上的网络编程,但是socket在上述的模型的什么位置呢。这个位置被一个天才的理论家或者是抽象的计算机大神提出并且安排出来。

在这里插入图片描述
要想理解socket编程怎么通过socket关键词实现服务器和客户端通讯,必须得实现的了解tcp/ip是怎么通讯的,在这个的基础上在去理解socket的握手通讯。

在tcp/ip协议中,tcp通过三次握手建立起一个tcp的链接,大致如下:

  1. 第一次握手:客户端尝试连接服务器,向服务器发送syn包,syn=j,客户端进入SYN_SEND状态等待服务器确认
  2. 第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
  3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

TCP三次握手如下图:
在这里插入图片描述
根据tcp的三次握手,socket也定义了三次握手,也许是参考tcp的三次握手,一些计算机大神们画出了socket的三次握手的模型图如下:

在这里插入图片描述
通过上面的图,我们清楚,我们好比一些泛型的程序员,一些理论提供者提供给了我们上面的图形的理论,我们需要做的就是讲上面的图形的抽象化的东西具体化:

  1. 第一次握手:客户端需要发送一个syn j 包,试着去链接服务器端,于是客户端我们需要提供一个链接函数
  2. 第二次握手:服务器端需要接收客户端发送过来的syn J+1 包,然后在发送ack包,所以我们需要有服务器端接受处理函数
  3. 第三次握手:客户端的处理函数和服务器端的处理函数

三次握手只是一个数据传输的过程,但是,我们传输前需要一些准备工作,比如将创建一个套接字,收集一些计算机的资源,将一些资源绑定套接字里面,以及接受和发送数据的函数等等,这些功能接口在一起构成了socket的编程。

下面大致的按照客户端和服务端将所需的函数详细的列举出来:
在这里插入图片描述

2 代码

先上一段简单的代码:
server.cpp:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
    int  listenfd, connfd;
    struct sockaddr_in  servaddr;
    char buff[4096];
    char send_buf[4096] = " Hello ,Client !!!";
    int  n;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
    {
        printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(6666);

    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
    {
        printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }

    if( listen(listenfd, 10) == -1)
    {
        printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }

    printf("======waiting for client's request======\n");

    while(1)
    {
        if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1)
        {
            printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
            continue;
        }
        
        if( send(connfd, send_buf, strlen(send_buf), 0) < 0)
		{
	    	printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
            return -1;
		}
        n = recv(connfd, buff, MAXLINE, 0);
        buff[n] = '\0';
        printf("recv msg from client: %s\n", buff);
        close(connfd);
    }
    close(listenfd);
    return 0;
}

client.cpp:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
    int   sockfd, n;
    char  recvline[4096], sendline[4096];
    struct sockaddr_in  servaddr;

    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
        return 0;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(6666);

    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    {
        printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }

    n = recv(sockfd, recvline, MAXLINE, 0);
    recvline[n] = '\0';
    printf("recv msg from client: %s\n", recvline);

    printf("send msg to server: \n");
    fgets(sendline, 4096, stdin);
    if( send(sockfd, sendline, strlen(sendline), 0) < 0)
    {
        printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    close(sockfd);
    return 0;
}

3 代码说明

服务器端:

其过程是首先服务器方要先启动,并根据请求提供相应服务:

  1. 打开一通信通道并告知本地主机,它愿意在某一公认地址上的某端口(如FTP的端口可能为21)接收客户请求;
  2. 等待客户请求到达该端口;
  3. 接收到客户端的服务请求时,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止;
  4. 返回第(2)步,等待另一客户请求;
  5. 关闭服务器。

客户端:

  1. 打开一通信通道,并连接到服务器所在主机的特定端口;
  2. 向服务器发服务请求报文,等待并接收应答;继续提出请求…
  3. 请求结束后关闭通信通道并终止。

从上面所描述过程可知:

  1. 客户与服务器进程的作用是非对称的,因此代码不同。
  2. 服务器进程一般是先启动的。只要系统运行,该服务进程一直存在,直到正常或强迫终止。

4 API函数说明

1 创建套接字─socket()

应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,Ubuntu中其调用格式如下(windows的没做,大致差不多,要调用win下面的一个库,具体的可以自行搜索):

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

使用此函数,需要包含以下头文件:

#include <sys/types.h> 
#include <sys/socket.h>

调用此函数会创建一个通讯端点,且函数调用后,创建成功会返回一个文件描述符,失败会返回 -1。

参数domain定义socket的通讯域,选择一个协议族,常用的协议族有,其他的我用不上,也就没看:

  • AF_INET IPv4 Internet protocols
  • AF_INET6 IPv6 Internet protocols

参数 type 描述要建立的套接字的类型,常用的如下所示,其他的我也用不上:

  1. TCP流式套接字(SOCK_STREAM)
    是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。
    SOCK_STREAM有以下几个特征:

    • 数据在传输过程中不会消失;
    • 数据是按照顺序传输的;
    • 数据的发送和接收不是同步的(有的教程也称“不存在数据边界”)。
  2. 数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,在代码中使用 SOCK_DGRAM 表示。
    计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。

    因为数据报套接字所做的校验工作少,所以在传输效率方面比流格式套接字要高。
    SOCK_DGRAM有以下特征:

    • 强调快速传输而非传输顺序;
    • 传输的数据可能丢失也可能损毁;
    • 限制每次传输的数据大小;
    • 数据的发送和接收是同步的(有的教程也称“存在数据边界”)。
  3. 原始式套接字(SOCK_RAW)该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。

参数protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。

  • 如果使用TCP协议的话,设置为 IPPROTO_TCP;
  • 如果使用UDP协议的话,设置为 IPPROTO_UDP;
    这里说明一下,SOCK_STREAM对应的协议类型为IPPROTO_TCP,SOCK_DGRAM对应的协议类型为IPPROTO_UDP,不可以混着用的。

根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。

2 绑定本地地址─bind()

当一个套接字用socket()创建后,存在一个名字空间(地址族),但它没有绑定一个socket地址。bind()将套接字地址(包括本地主机地址和本地端口地址),根据sockfd描述符,将所创建的套接字号与一个地址绑定起来。其调用格式如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

需要包含头文件:

#include <sys/types.h>   
#include <sys/socket.h>  
  • 参数sockfd是由socket()调用返回的并且未作连接的套接字描述符(套接字号);
  • 参数addr 是赋给套接字sockfd的本地地址(名字),其长度可变,结构随通信域的不同而不同;
  • 参数addrlen表明了name的长度。

这里看一下代码:

struct sockaddr_in  servaddr; 

servaddr 在被创建的时候,结构体是 sockaddr_in 类型,但是在bind()函数中,addr是 sockaddr 类型,一定要进行类型转换。

如果没有错误发生,bind()返回0,否则返回-1。

3 建立套接字连接─connect()与accept()

这两个系统调用用于完成一个完整相关的建立,其中connect()用于建立连接。accept()用于使服务器等待来自某客户进程的实际连接。

1 connect()

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

头文件

#include <sys/types.h>
#include <sys/socket.h>

此函数会连接 sockfd描述符对应的 socket 到地址 addr。
如果没有错误发生,connect()返回0,否则返回-1。

2 accept()

accept()的调用格式如下:

int accept(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数 sockfd 为本地套接字描述符,在用做accept()调用的参数前应该先调用过listen()。
参数 addr 指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。
参数 addrlen 为客户方套接字地址的长度(字节数)。

四个套接字系统调用,socket()、bind()、connect()、accept(),可以完成一个完全五元相关的建立。

4 监听连接─listen()

此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用,其调用格式如下:

int listen(int sockfd, int backlog);

头文件

#include <sys/types.h>       
#include <sys/socket.h>

参数 sockfd 标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。
参数 backlog 表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。
如果没有错误发生,listen()返回0,否则它返回-1。

listen()在执行调用过程中可为没有调用过 bind() 的套接字 sockfd 完成所必须的连接,并建立长度为backlog 的请求连接队列。

调用listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用 bind() 给 sockfd 赋于一个名字之后调用,而且一定要在 accept() 之前调用。

5 数据传输─send()与recv()

1 send()

ssize_t send(int sockfd, void *buf, size_t len, int flags);

头文件

#include <sys/types.h>
#include <sys/socket.h>

在建立一个 socket 连接后,可以调用此函数去发送数据。
参数 sockfd 为建立好的 socket 文件描述符;
参数 buf 为一个指向需要发送数据的指针,
参数 len 为发送数据的长度;
参数 flags 一般为 0 即可。

socket 传送数据都是 byte 方式传送,这里的数据长度,和传送数据,都是以字节长度来计算的。

调用此函数成功后,会返回 发送的数据长度,失败,则返回 -1。

2 recv()

ssize_t send(int sockfd, void *buf, size_t len, int flags);

头文件

#include <sys/types.h>
#include <sys/socket.h>

在建立一个 socket 连接后,可以调用此函数接收数据。具体参数和用法 与 send() 类似,这里就不再多说了。

6 关闭—close()

closet()关闭套接字fd,并释放分配给该套接字的资源;如果fd涉及一个打开的TCP连接,则该连接被释放。closet()的调用格式如下:

int close(int fd);

头文件

#include <unistd.h>

closet()成功,函数返回0,失败返回 -1。

7 文件控制函数—fcntl

Linux的核心思想就是一切皆可文件,这个函数就是根据文件描述词来操作文件的特性。其他功能我也没咋用过,暂时就不说了,这里主要介绍下,如何通过此函数设置socket阻塞和非阻塞。
fcntl的调用格式:

int fcntl(int fd, int cmd, ... /* arg */ );

头文件

#include <unistd.h>
#include <fcntl.h>

fcntl()函数可以对文件描述符 fd 进行参数 cmd 代表的操作方式对 fd 进行 可选参数 arg 表示的设置。

fcntl函数有5种功能:

  1. 复制一个现有的描述符(cmd=F_DUPFD)
  2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
  4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  5. 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

cmd选项:

F_DUPFD 返回一个如下描述的(文件)描述符:

  1. 最小的大于或等于arg的一个可用的描述符
  2. 与原始操作符一样的某对象的引用
  3. 如果对象是文件(file)的话,返回一个新的描述符,这个描述符与arg共享相同的偏移量(offset)
  4. 相同的访问模式(读,写或读/写)
  5. 相同的文件状态标志(如:两个文件描述符共享相同的状态标志)
  6. 与新的文件描述符结合在一起的close-on-exec标志被设置成交叉式访问execve的系统调用

F_GETFD 取得与文件描述符fd联合close-on-exec标志,类似FD_CLOEXEC.如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg被忽略)

F_SETFD 设置close-on-exec标志。该标志以参数arg的FD_CLOEXEC位决定。

F_GETFL 取得fd的文件状态标志,如同下面的描述一样(arg被忽略)

F_SETFL 设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC。

F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略)

F_SETOWN 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明,否则,arg将被认为是进程id

命令字(cmd) F_GETFL和F_SETFL的可选参数如下面的描述:

  • O_NONBLOCK
    非阻塞I/O;如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误
  • O_APPEND 强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志
  • O_DIRECT 最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据.
    如果不能够避免缓存,那么它将最小化已经被缓存了的数 据造成的影响.如果这个标志用的不够好,将大大的降低性能
  • O_ASYNC 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候

注意: 在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。

fcntl的返回值: 与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD,F_GETFD,F_GETFL以及F_GETOWN。第一个返回新的文件描述符,第二个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。

8 setsockopt设置socket选项

函数调用格式:

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

包含的头文件:

#include <sys/types.h>
#include <sys/socket.h>

下面的内容是我从另一个大神那拷贝过来的。
具体用法:

  1. closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(BOOL)); 
  1. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt(s, SOL_SOCKET, SO_DONTLINGER, (const  char*)&bDontLinger, sizeof(BOOL));
  1. 在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒 
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char*)&nNetTimeout,sizeof(int)); 
//接收时限 
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char*)&nNetTimeout,sizeof(int));
  1. 在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区    
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
  1. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响程序的性能:
 int nZero=0; 
    setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
  1. 同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
  1. 一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;    
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
  1. 在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
  1. 如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体应用的要求(即让没发完的数据发送出去后在关闭socket)
struct linger { u_short l_onoff; u_short l_linger; };

linger m_sLinger;
// (在closesocket()调用,但是还有数据没发送完毕的时候容许逗留。如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
m_sLinger.l_onoff=1;
m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

SO_LINGER此选项指定函数close对面向连接的协议如何操作(如TCP)。缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据 发送给对方。
SO_LINGER选项用来改变此缺省设置。使用如下结构:

struct linger {
     int l_onoff; 
     int l_linger; 
};

有下列三种情况:

  1. l_onoff为0,则该选项关闭,l_linger的值被忽略,等于缺省情况,close立即返回;
  2. l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST 给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
  3. l_onoff为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。

上述的并不是所有功能,主要功能也就是这些了。

最后,再上一个UDP的例子,有爱自取:

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>

#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <array>

int main()
{
    std::string local_ip = "192.168.6.100";
    std::uint16_t local_port = 8001;
    std::string listen_ip = "192.168.6.104";
    std::uint16_t listen_port = 4001;
    std::string send_ip = "192.168.6.104";
    std::uint16_t send_port = 4002;
    std::cout << "Local IP " << local_ip << " Port " << local_port << std::endl;
    std::cout << "Listen IP " << listen_ip << " Port " << listen_port << std::endl;
    std::cout << "Send IP " << send_ip << " Port " << send_port << std::endl;

    sockaddr_in local_address;
    local_address.sin_family = AF_INET;
    local_address.sin_port = htons(local_port);
    local_address.sin_addr.s_addr = inet_addr(local_ip.c_str());
    socklen_t local_addr_length = sizeof(local_address);

    sockaddr_in listen_address;
    listen_address.sin_family = AF_INET;
    listen_address.sin_port = htons(listen_port);
    listen_address.sin_addr.s_addr = inet_addr(listen_ip.c_str());
    socklen_t listen_addr_length = sizeof(listen_address);

    sockaddr_in send_address;
    send_address.sin_family = AF_INET;
    send_address.sin_port = htons(send_port);
    send_address.sin_addr.s_addr = inet_addr(send_ip.c_str());
    socklen_t send_addr_length = sizeof(send_address);

    int socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(socket_fd < 0)
    {
		perror("socket");
    }

    int flags = fcntl(socket_fd, F_GETFL, 0);
    if(flags < 0)
    {
        perror("fcntl");
        return -1;
    }
    flags |= O_NONBLOCK;
    if(fcntl(socket_fd, F_SETFL, flags) < 0)
    {
        perror("fcntl");
        return -1;
    }

    struct sockaddr* addr = nullptr;
    socklen_t size = 0;
    addr = (struct sockaddr*)&local_address;
    size = sizeof(local_address);
    if (bind(socket_fd, addr, size) < 0 ) 
    {
        std::cout << "Failed to bind socket." << std::endl;
		perror("bind");
		return -1;
    }
    else
    {
		std::cout << "Succeeded to bind socket." << std::endl;
    }

    uint8_t rec_buffer[13];
    int rec_length;
    int send_length;

    while(1)
    {
		rec_length = recvfrom(socket_fd, rec_buffer, sizeof(rec_buffer), 0, (struct sockaddr *) &listen_address, &listen_addr_length);
 
        if(rec_length < 0)
        {
		    //perror("recvfrom");
		    //return -1;
		    continue;
        }
        printf("rec_length = %d\n", rec_length);
        for(int i = 0;i < rec_length;i++)
        {
	    	printf("rec buffer[%d] %x\n",i,rec_buffer[i]);
        }

		send_length = sendto(socket_fd, rec_buffer, sizeof(rec_buffer), 0, (struct sockaddr *) &send_address, send_addr_length);
		printf("send_length = %d \n", send_length);	

		printf("\n");
		sleep(0.02);
    }

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值