C语言 进程通讯 socket套接字(TCP/UDP)示例

本文介绍了C语言中进程通信的socket套接字使用,涉及TCP和UDP协议。讲解了主机字节序与网络字节序的概念、端口号的转换以及IP地址的转换函数。此外,还详细阐述了TCP/IP协议族各层的主要协议,以及TCP与UDP的通讯流程,包括socket创建、bind、listen、accept、connect、send、recv和关闭连接的方法。并提供了服务端和客户端的示例代码。

主机字节序(host-byte):指处理器存储数据的字节顺序,分两种

        大端存储(big-endian):低地址存储数据高位(符合书写规则),由ARM、Motorola等采用

        小端存储(little-endian):低地址存储数据低位(将数据不重要的部分保存在低地址,重要的部分保存在高地址),由Intel、AMD等采用

如何测出主机字节序

#include <stdio.h>
int main(){
    int a = 0x12345678;
    char *p = (char *)&a;
    if(*p == 0x78){
        puts("小端");
    }else if(*p == 0x12){
        puts("大端");
    }
    return 0;
}

网络字节序(network-byte):指在网络编程中存储数据的字节顺序(大端存储),将数据重要的部分保存在低地址,不重要的部分保存到高地址,端口号(固定进程号)IP地址都需要网络字节序

端口号一般由IANA (Internet Assigned Numbers Authority) 管理,类型为unsigned short,最大值是65535,可在 /etc/services 文件中查看系统已被占用的端口号。拥有网络通讯功能的进程都会有端口号,用于传输层向应用层传递数据时找到目标进程

1~1023(1~255为常见服务端口,256~1023端口通常由UNIX系统占用)为常用端口,已被世界公认的各种服务征用,最好不要使用

1024~49151 为已登记端口,默认是给服务器程序使用的,但个人电脑一般不会安装服务器程序,只会安装客户端程序,所以平时编写程序时可以使用

49152~65535 为动态或私有端口,给客户端程序使用,一般是自动分配,因为客户端不绑定固定的IP和端口

端口号由主机字节序转换为网络字节序,可能需要配合 atoi() (字符串转换为整型)函数,头文件<netinet/in.h>:

16位无符号数转换

u_short htons(u_short short)

32位无符号数转换

u_long htonl(u_long hostlong)

返回值:网络字节序的无符号数(不是字符串)

端口号由网络字节序转换为主机字节序:

16位无符号数转换

u_short ntohs(u_short short)

32位无符号数转换

u_long ntohl(u_long hostlong)

返回值:主机字节序的无符号数(不是字符串)

IP地址的转换,头文件<arpa/inet.h>:

点分十进制的IPv4地址(127.0.0.1)被视为一个4字节 int 整型(32位),把IP地址的四个部分都分别转换成一个8位的二进制数(01111111   00000000   00000000   00000001),再直接合一起成为一个数(2130706433),就是IP地址

将IP地址的点分十进制转换成网络字节序

此方法有弊端,若字符串有效,返回32位网络字节序二进制数,否则返回INADDR_NONE = (32个1),占用了255.255.255.255

in_addr_t inet_addr(const char *strptr)

返回值:永远成功,返回网络字节序的32位无符号 int 型 IP 地址

IPv4也可以用这个方法

int inet_aton(const char *strptr, struct in_addr *addrptr)

strptr:IP地址的点分十进制字符串

addrptr:存储转换好的32位的网络字节序二进制数

返回值:成功 = 1,失败 = 0

将网络字节序的IP地址转换成点分十进制

char *inet_ntoa(struct in_addr inaddr)

返回值:一个点分十进制数 字符串的指针

随着IPv6(16字节 128位)出现了两个新函数,兼容IPv4地址和IPv6地址,函数名中p是表达(presentation),n是数值(numeric)

将IP地址的点分十进制转换成网络字节序

int inet_pton(int family, const char *strptr, void *addrptr)

family:IPv4或6,填 AF_INET 或 AF_INET6

strptr:IP地址的点分十进制字符串

addrptr:存储转换好的网络字节序二进制数

返回值:成功 = 1,非有效字符串 = 0,失败 = -1

示例

sockaddr_in addrServer;
inet_pton(AF_INET, "127.0.0.1", &addrServer.sin_addr);
addrServer.sin_port = htons(6666);  // 该函数需要添加头文件: #include <winsock.h>
addrServer.sin_family = AF_INET;

将网络字节序的IP地址转换成点分十进制

const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len)

family:IPv4或6,填 AF_INET 或 AF_INET6

addrptr:网络字节序的IP地址

strptr:存储转换好的IP地址点分十进制数,必须分配内存,指定大小

len:strptr的大小,以免溢出,若len太小不足以容纳转换的结果(包括结尾 ‘\0’ ),则返回空指针,置位错误码errno = ENOSPC

返回值:成功 = strptr,失败 = NULL

示例

char buf[20] = {0};
inet_ntop(AF_INET, &recvaddr.sin_addr, buf, sizeof(buf));

TCP/IP协议族

TCP/IP协议族之所以能实现网络通讯,依靠的是网络层的IP协议,只是IP协议仅能做最基本的通讯,缺乏很多精细的功能,这需要传输层的TCP或UDP来提供

应用层:

HTTP(Hypertext Transfer Protocol) 超文本传输协议:万维网的数据通信的基础

FTP(File Transfer Protocol) 文件传输协议:网络上文件传输的一套标准协议,使用TCP传输

TFTP(Trivial File Transfer Protocol) 简单文件传输协议:网络上文件传输的一套标准协议,使用UDP传输

SMTP(Simple Mail Transfer Protocol) 简单邮件传输协议:可靠且有效的电子邮件传输的协议

传输层:

TCP(Transport Control Protocol) 传输控制协议:面向连接的、有序的、双向通讯的、可靠的字节流通讯,并支持带外数据

UDP(User Datagram Protocol) 用户数据报协议:无连接的、不可靠的、固定长度的数据报通讯

网络层:

IP(Internetworking Protocol) 网际互连协议:在多个不同网络间实现信息传输的协议

ICMP(Internet Control Message Protocol) 互联网控制信息协议:在IP主机、路由器之间传递控制消息----ping命令

IGMP(Internet Group Management Protocol) 互联网组管理:组播协议,用于主机和组播路由器之间通信

链路层:

ARP(Address Resolution Protocol) 地址解析协议:通过IP地址获取对方mac地址

RARP(Reverse Address Resolution Protocol) 逆向地址解析协议:通过mac地址获取对方ip地址

TCP流程

创建一个套接字文件,将复杂的网络通信过程简化成普通的文件I/O操作过程,打开文件 -> 读/写 -> 关闭文件

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol)

domain:协议族,AF_xxx是地址族(address family),指明用什么协议的IP地址格式,PF_xxx是协议族(protocol family),指明用什么协议族,但是两种宏的值是一样的(如AF_UNIX == PF_UNIX),这是因为每种协议族都各只有一种IP地址格式,因此它们其实是一一对应的关系

AF_PACKET

原始套接字
AF_UNIX 或 AF_LOCAL本机进程间通讯

AF_INET

IPv4

AF_INET6

IPv6
PF_IPX(几乎不用)novell 协议族,局域网专用,效率高,windows似乎不支持,linux支持
PF_APPLETALK苹果公司的局域网协议族
AF_UNSPEC不指定具体协议族,通过第三个参数protocol协议编号来指定用什么协议族中的哪个子协议

type:套接字类型,进一步指定协议族中的哪个子协议

SOCK_RAW

原始网络通讯(在TCP/IP协议族中是跳过传输层直接到网络层的IP协议,用IP协议通讯,一般用于想由自行编写的应用程序来实现类似传输层的TCP或UDP的功能的情况)

SOCK_STREAM

(流式套接字)

使用面向连接的、有序的、双向通讯的、可靠的字节流通讯,并支持带外数据(在TCP/IP协议族中只有TCP(内置流量控制,避免数据流淹没慢的接收方,数据无长度限制)符合)

SOCK_DGRAM

(数据报套接字)

使用无连接的、不可靠的、固定长度的数据报通讯(在TCP/IP协议族中只有UDP符合)
SOCK_NONBLOCK对socket函数返回的文件描述符文件所执行的任何操作均由默认的阻塞改为非阻塞,可以和前面的宏一起用 | 运算
SOCK_CLOEXEC一旦进程调用exec执行新进程后,自动关闭socket函数返回的文件描述符,可以和前面的宏一起用 | 运算

protocol:协议编号,每个协议的唯一识别号。0 = 使用 domain 和 type 参数指定的协议,但当 domain 和 type 所指的协议有多个选择时,需要该参数来区分,所有协议的协议编号都保存在 /etc/protocols 下

返回值:成功 = 文件描述符,失败 = -1 并置位errno错误码

将指定了通讯协议的socket套接字文件与自己的IP、端口进行绑定,建立固定的对应关系

服务端的TCP/IP协议族需要bind,而本机进程通讯需要bind到系统绝对路径下的某个文件上。若不明确的调用bind,即未完成socket套接字文件与固定的IP和端口绑定,则会自动指定一个IP和端口,且每次通讯都会重新指定,因此这种TCP服务器会导致客户端的三次握手失败

客户端的TCP/IP协议族不需要bind,但本机进程通讯需要bind,TCP的connect会三次握手且内核会自动生成本地端口并带上本地IP;UDP的sendto有目标地址和目标端口,内核会自动生成本地端口且带上本地IP;而本机进程通讯的客户端只能通过bind到同一个系统绝对路径文件才能发送出去,所以需要bind

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

sockfd:socket函数返回的文件描述符

addr:要绑定的自己的网络信息结构体,把struct sockaddr_in结构体(IPv4地址结构)强转为struct sockaddr结构体(通用地址结构)

struct sockaddr {
    unsigned short sa_family; // 地址族
    char sa_data[14];         // 端口号和IP地址
};
struct sockaddr_in {
    unsigned short sin_family;    //地址族
    unsigned short sin_port;      //端口号(网络字节序)htons(8888)
    struct in_addr sin_addr;      //IP地址(网络字节序)inet_addr("127.0.0.1")
    unsigned char sin_zero[8];    //为了使sockaddr与sockaddr_in保持相同大小而保留的空字节
};
struct in_addr {
    unsigned int s_addr; // IP地址
};

addrlen:网络信息结构体字节数

返回值:成功 = 0,失败 = -1 并置位errno错误码

将socket套接字设置成被动的,监听客户端连接的状态,只用于TCP服务器。socket套接字文件描述符默认是主动的,即可以主动向对方发送数据,转换为被动后,只能在接收到数据后才能应答,这样才能实现TCP连接的三次握手章程

int listen(int sockfd, int backlog)

sockfd:socket函数返回的文件描述符

backlog:监听队列长度,记录正在连接,但未连接成功的客户端,小于30即可

返回值:成功 = 0,失败 = -1 并置位errno错误码

阻塞被动监听客户端的连接,三次握手成功后,会返回一个新文件描述符,专门用于和该客户端通讯,对于正在连接的客户端会被记录到监听队列中,若 socket 函数中 type 参数设置为SOCK_NONBLOCK 非阻塞,则此函数也变为非阻塞,只用于TCP服务器

int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len)

sockfd:已被listen函数转换为被动监听的文件描述符,若未转换,失败

address:用于记录存放发起连接请求的客户端的 IP 和端口,需把struct sockaddr_in结构体(IPv4地址结构)强转为struct sockaddr结构体(通用地址结构),也可传NULL

address_len:客户端信息结构体字节数,但要求传的是地址,所以之前需要申请一个 socklen_t 类型的参数来获取 sizeof(clientaddr) 的值,若address填NULL,此处也填NULL

返回值:成功 = 与该客户端通讯专用的新文件描述符,失败 = -1 并置位errno错误码

只用于TCP客户端,客户端向服务器发起连接请求

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

sockfd:socket函数返回的文件描述符

addr:存放服务器网络信息的结构体,用于记录连接的哪个服务器

addrlen:网络信息结构体字节数

返回值:成功 = 0,失败 = -1 并置位errno错误码

发送消息,属于系统调用

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

sockfd:accept函数(服务器)或socket函数(客户端)返回的文件描述符

buf:要发送的消息首地址,正规操作应当使用结构体来封装数据,并注意要主机字节序和网络字节序的转换,需强转成 void * 类型并 & 取址

len:buf 参数的字节数

flags:

0 (常用)忽略此参数,send 函数与 write 函数效果相同
MSG_CONFIRM告诉链路层已成功获得另一方的回复,若链路层没有检测到,将重新检测,仅对数据报套接字 SOCK_DGRAM 和 原始网络通讯 SOCK_RAW 有效,仅对 IPv4 和 IPv6 实现
MSG_DONTROUTE不要使用网关来发送数据,只发送到直接连接的主机上。通常只有诊断或者路由程序会使用,这只针对路由的协议族定义的,数据包的套接字没有
MSG_DONTWAIT (常用)非阻塞,若将被阻塞则调用失败返回 EAGAIN 或 EWOULDBLOCK
MSG_EOR终止一条记录,只有部分套接字支持此功能如 SOCK_SEQPACKET
MSG_MORE

TCP中使用:调用方有更多的数据要发送,每次的调用都可设置

UDP中使用:告诉内核将所有设置了此标志位要发送的数据打包成一个单一的数据报,此数据报只有在执行没有设置此标志位的调用时才传输

MSG_NOSIGNAL (常用)面向流的套接字中对端已关闭,不要生成SIGPIPE信号,返回EPIPE错误。此设置影响此进程中所有线程
MSG_OOB (常用)发送带外数据,需要套接字类型支持此功能(如 SOCK_STREAM 类型),也需要底层协议也支持带外数据

返回值:成功 = 发送的字节数,失败 = -1 并置位errno错误码,超时或对端主动关闭 = 0

接收消息

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

sockfd:accept函数(服务器)或socket函数(客户端)返回的文件描述符

buf:存放接收的消息的缓冲区,并注意要网络字节序和主机字节序的转换

len:buf 参数的字节数

flags:

0 (常用)忽略此参数,recv 函数与 read 函数相同
MSG_CMSG_CLOEXEC设置close-on-exec标志位,在执行exec函数族后关闭返回的套接字描述符
MSG_DONTWAIT (常用)非阻塞,若将被阻塞则调用失败并返回EAGAIN 或 EWOULDBLOCK
MSG_ERRQUEUE从套接字错误队列中接收错误信息
MSG_OOB (常用)接收带外数据,需要套接字类型支持此功能(如 SOCK_STREAM 类型),也需要底层协议也支持带外数据
MSG_PEEK接收操作改为返回接收队列的首位数据,但不将该数据从队列中移除。因此后续的接收调用将返回相同的数据
MSG_TRUNC用于原始套接字(AF_PACKET)、Internet数据报、netlink、UNIX数据报套接字中,返回数据包或数据报的实际长度,即使它比传递的缓冲区长
MSG_WAITALL阻塞等待直到完成整个请求,但在被信号打断、发生错误或断开连接、下一个要接收的数据与返回的数据类型不同时,会返回少于请求的数据。此标志对数据报套接字不起作用。

返回值:成功 = 接收的字节数,失败 = -1 并置位errno错误码,超时或对端主动关闭 = 0

close 函数断开连接

缺点:

        1.会将 读写 全关闭,无法实现分开关闭

        2.若一个连接中有多个文件描述符(1.dup复制;2.子进程从父进程中继承)时,只有关闭了所有文件描述符后连接才会关闭

int close(int fd)

shutdown 函数断开连接(正规方法)

可按照要求关闭连接,且只要 shutdown 关闭其中一个文件描述符,整个连接会立即断开

#include <sys/socket.h>
int shutdown(int sockfd, int how)

sockfd:accept函数(服务器)或socket函数(客户端)返回的文件描述符

how:如何断开连接

SHUT_RD断开读
SHUT_WR断开写
SHUT_RDWR断开读写

返回值:成功 = 0,失败 = -1 并置位errno错误码

示例

服务端

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

#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s-%s(%d)\n",__FILE__,__func__,__LINE__);\
                            exit(-1);\
                        }while(0)

int main(int argc, char const *argv[])
{
    if(3 != argc){
        printf("Usage : %s <ip> <port>\n",argv[0]);
        exit(-1);
    }

    int socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == socketfd){
        ERRLOG("socket error");
    }

    struct sockaddr_in serveraddr = {0};
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(-1 == bind(socketfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))){
        ERRLOG("bind error");
    }

    if(-1 == listen(socketfd,5)){
        ERRLOG("listen error");
    }

    struct sockaddr_in clientaddr;
    memset(&clientaddr,0,sizeof(clientaddr));

    socklen_t clientaddrlen = sizeof(clientaddr);
    char buff[128] = {0};
    int acceptfd = 0;
    int recvfd = 0;
    while(1){
        acceptfd = accept(socketfd,(struct sockaddr*)&clientaddr,&clientaddrlen);
        if(-1 == acceptfd){
            ERRLOG("accept error");
        }
        printf("客户端 %s:%d 连接到服务器了\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

        while(1){
            if(0 > (recvfd = recv(acceptfd,buff,128,0))){
                ERRLOG("recv error");
            }if(0 == recvfd){
                printf("客户端 %s:%d 断开了连接\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
                break;
            }else{
                if(0 == strcmp(buff,"quit")){
                    printf("客户端 %s:%d 退出了\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
                    break;
                }
                printf("%s-%d:[%s]\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),buff);
                strcat(buff,"--client");
                if(-1 == send(acceptfd,buff,128,0)){
                    ERRLOG("send error");
                }
            }
        }
        close(acceptfd);
    }
    shutdown(socketfd, SHUT_RDWR);
    return 0;
}

客户端

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

#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s-%s(%d)\n",__FILE__,__func__,__LINE__);\
                            exit(-1);\
                        }while(0)
                    
int main(int argc, char const *argv[])
{
    if(3 != argc){
        printf("Usage : %s <ip> <port>\n",argv[0]);
        exit(-1);
    }

    int socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == socketfd){
        ERRLOG("socker error");
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = ntohs(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(-1 == connect(socketfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))){
        ERRLOG("connect error");
    }

    char buff[128] = {0};
    while(1){
        fgets(buff,128,stdin);
        buff[strlen(buff)-1] = '\0';
        if(-1 == send(socketfd,buff,128,0)){
            ERRLOG("send error");
        }
        if(0 == strcmp(buff,"quit")){
            break;
        }
        if(-1 == recv(socketfd,buff,128,0)){
            ERRLOG("recv error");
        }
        printf("收到回复:[%s]\n",buff);
    }
    close(socketfd);

    return 0;
}

UDP流程

UDP中发送消息

在TCP(SOCK_STREAM, SOCK_SEQPACKET)下使用,参数dest_addr和addrlen被忽略,若不置为null和0,则返回错误EISCONN,若置为NULL和0后并不处于连接状态,同样返回错误EISCONN

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)

sockfd:socket函数返回的文件描述符

buf:要发送的消息首地址,正规操作应当使用结构体来封装数据,并注意要主机字节序和网络字节序的转换,需强转成 void * 类型并 & 取址

len:buf 参数的字节数

flags:

0 (常用)忽略此参数
MSG_CONFIRM告诉链路层已成功获得另一方的回复,若链路层没有检测到,将重新检测,仅对数据报套接字 SOCK_DGRAM 和 原始网络通讯 SOCK_RAW 有效,仅对 IPv4 和 IPv6 实现
MSG_DONTROUTE不要使用网关来发送数据,只发送到直接连接的主机上。通常只有诊断或者路由程序会使用,这只针对路由的协议族定义的,数据包的套接字没有
MSG_DONTWAIT (常用)非阻塞,若将被阻塞则调用失败返回 EAGAIN 或 EWOULDBLOCK
MSG_EOR终止一条记录,只有部分套接字支持此功能如 SOCK_SEQPACKET
MSG_MORE

TCP中使用:调用方有更多的数据要发送,每次的调用都可设置

UDP中使用:告诉内核将所有设置了此标志位要发送的数据打包成一个单一的数据报,此数据报只有在执行没有设置此标志位的调用时才传输

MSG_NOSIGNAL (常用)面向流的套接字中对端已关闭,不要生成SIGPIPE信号,返回EPIPE错误。此设置影响此进程中所有线程
MSG_OOB (常用)发送带外数据,需要套接字类型支持此功能(如 SOCK_STREAM 类型),也需要底层协议也支持带外数据

dest_addr:接收方的网络信息结构体,需把struct sockaddr_in结构体(IPv4地址结构)强转为struct sockaddr结构体(通用地址结构),表示发送给谁

addrlen:网络信息结构体字节数

返回值:成功 = 发送的字节数,失败 = -1 并置位errno错误码,超时或对端主动关闭 = 0

UDP中接收消息

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)

sockfd:socket函数返回的文件描述符

buf:存放接收的消息的缓冲区,并注意要网络字节序和主机字节序的转换

len:buf 参数的字节数

flags:

0 (常用)忽略此参数
MSG_CMSG_CLOEXEC设置close-on-exec标志位,在执行exec函数族后关闭返回的套接字描述符
MSG_DONTWAIT (常用)非阻塞,若将被阻塞则调用失败并返回EAGAIN 或 EWOULDBLOCK
MSG_ERRQUEUE从套接字错误队列中接收错误信息
MSG_OOB (常用)接收带外数据,需要套接字类型支持此功能(如 SOCK_STREAM 类型),也需要底层协议也支持带外数据
MSG_PEEK接收操作改为返回接收队列的首位数据,但不将该数据从队列中移除。因此后续的接收调用将返回相同的数据
MSG_TRUNC用于原始套接字(AF_PACKET)、Internet数据报、netlink、UNIX数据报套接字中,返回数据包或数据报的实际长度,即使它比传递的缓冲区长
MSG_WAITALL阻塞等待直到完成整个请求,但在被信号打断、发生错误或断开连接、下一个要接收的数据与返回的数据类型不同时,会返回少于请求的数据。此标志对数据报套接字不起作用。

src_addr:发送方的网络信息结构体,需把struct sockaddr_in结构体(IPv4地址结构)强转为struct sockaddr结构体(通用地址结构),表示从谁那接收

addrlen:发送方的网络信息结构体字节数,但要求传的是地址,所以之前需要申请一个 socklen_t 类型的参数来获取 sizeof(clientaddr) 的值

返回值:成功 = 接收的字节数,失败 = -1,超时或对端主动关闭 = 0

示例

服务端

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

int main(int argc, char const *argv[])
{
    if(3 != argc){
       printf("Usage : %s <ip> <port>\n",argv[0]);
       exit(-1); 
    }

    int socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == socketfd){
        perror("socker error");
        printf("%s--%s(%d)\n",__FILE__,__func__,__LINE__);
        exit(-1);
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(-1 == bind(socketfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))){
        perror("bind error");
        printf("%s--%s(%d)\n",__FILE__,__func__,__LINE__);
        exit(-1);
    }

    struct sockaddr_in clientaddr;
    memset(&clientaddr,0,sizeof(clientaddr));

    socklen_t clientaddrlen = sizeof(clientaddr);
    char buff[128] = {0};
    while(1){
        if(-1 == recvfrom(socketfd, buff, 128, 0, (struct sockaddr*)&clientaddr,&clientaddrlen)){
            perror("recvfrom error");
            printf("%s--%s(%d)\n",__FILE__,__func__,__LINE__);
            exit(-1);
        }
        printf("%s(%d):%s\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),buff);
        strcat(buff,"--strcat");
        if(-1 == sendto(socketfd,buff,128,0,(struct sockaddr*)&clientaddr,clientaddrlen)){\
            perror("sendto error");
            printf("%s--%s(%d)\n",__FILE__,__func__,__LINE__);
            exit(-1);
        }
        memset(buff,0,128);
    }
    close(socketfd);

    return 0;
}

客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <error.h>

int main(int argc, char const *argv[])
{   
    if(3 != argc){
        printf("Usage : %s <ip> <port>\n",argv[0]);
        exit(-1);
    }

    int socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == socketfd){
        perror("socket error");
        printf("%s--%s(%d)\n",__FILE__,__func__,__LINE__);
        exit(-1);
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    socklen_t serveraddrlen = sizeof(serveraddr);
    char buff[128] = {0};
    while(1){
        puts("input your msg");
        fgets(buff, 128 ,stdin);
        buff[strlen(buff)-1] = '\0';
        if(0 == strcmp(buff,"quit")){
            break;
        }
        if(-1 == sendto(socketfd, buff, 128 ,0 ,(struct sockaddr*)&serveraddr,serveraddrlen)){
            perror("sendto error");
            printf("%s--%s(%d)\n",__FILE__,__func__,__LINE__);
            exit(-1);
        }
        if(-1 == recvfrom(socketfd,buff,128,0,(struct sockaddr*)&serveraddr,&serveraddrlen)){
            perror("recvform error");
            printf("%s--%s(%d)\n",__FILE__,__func__,__LINE__);
            exit(-1);
        }
        printf("recv:[%s]\n",buff);
        memset(buff,0,128);
    }
    close(socketfd);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值