Linux下的socket简单通信

1. 案例概述

采用主从模式,客户端向服务端发送自己的主机名称。该案例比较简单,目的是练习掌握熟悉套接字编程框架。
编写一个客户端和服务端,要求能够将客户端发送的信息显示出来。编译完在终端运行,端口号和IP地址可以在命令行中指定。

  1. 通信规程(应用层协议)
    客户端将信息发送到服务端,仅发送一次,服务端无反馈。

  2. 服务端(udp01_s.c)
    按主从模式的编程框架构建好后,主要侧重点是其数据传输部分,即通信规程的实现部分。
    该服务端只负责接收数据,不向客户端反馈任何数据。

  3. 客户端(udp01_c.c)
    与服务端类似,侧重点也是通信规程的实现部分。
    客户端只发送数据,不需要接收数据。

2. 代码(已做详细注释)

2.1 客户端代码

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

/*
    Server:
        command port [bindip = 0.0.0.0]

    Client:
        command port serverip
*/
//client客户端
int main(int argc, char **argv)//argc 是指传入参数的个数,argv[] 是一个指针数组,**argv 是一个指针数组指针
{
    const int SIZE_BUF = 1024;
    struct sockaddr_in addr1, addr2;//结构体,用来处理网络通信的地址
    socklen_t addrLen;//addrlen:dest结构体的长度,一般和sizeof()表示
    int sock = -1;
    char *buf = NULL;

    if(argc != 3)
    {
        printf("Usage: %s port serverip\n", argv[0]);
        return 0;
    }

    //fill server address填写服务器地址
    bzero(&addr1, sizeof(addr1));//初始化结构体 addr1
    /*
        原型:extern void bzero(void *s, int n);
        参数说明:s 要置零的数据的起始地址; n 要置零的数据字节个数。
        用法:#include <string.h>
        功能:置字节字符串s的前n个字节为零且包括‘\0’。
    */
    addr1.sin_family = AF_INET;//设置地址家族
    addr1.sin_port = htons(atoi(argv[1]));//设置端口
    //mysock.sin_addr.s_addr = inet_addr("192.168.1.0");  //设置地址
    //inet_aton 地址转换,将x.x.x.x形式的IP地址串转换为in_addr类型的结构体      成功返回1,失败返回0
    if(0 == inet_aton(argv[2], &addr1.sin_addr))//如果转换失败
    {
        printf("server-ip is invalid.\n");
        return 1;
    }

    sock = socket(AF_INET, SOCK_DGRAM, 0);//创建套接字(获得fd 套接字描述符)
    if(sock == -1)//如果失败
    {
        //for linux, perror("xxx") show error description "yyy" in format xxx: yyy
        perror("socket() fail ");
        return 2;
    }

    /*
    if(-1 == bind(sock, (struct sockaddr*)&addr1, sizeof(addr1)))
    {
        perror("bind() fail ");
        close(sock);
        return 3;
    }
    */

    buf = (char*)malloc(SIZE_BUF);


    addrLen = sizeof(addr2);
    //in block mode, wait till error occur, or data arrival
    //recvfrom(sock, buf, SIZE_BUF, 0, (struct sockaddr*)&addr2, &addrLen);

    sprintf(buf,"pid:%d", getpid());//将获取的进程识别码转存到buf中,并输出到屏幕上
    /*
        描述:C 库函数,发送格式化输出到 str 所指向的字符串。
        声明    int sprintf(char *str, const char *format, ...)
        参数
            str -- 这是指向一个字符数组的指针,该数组存储了 C 字符串。
            format -- 这是字符串,包含了要被写入到字符串 str 的文本。
    */
    /*
        函数功能:取得进程识别码
        函数说明:getpid函数用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题。
      返回值:目前进程的进程识别码
    */
    //如果发送数据失败
    if(-1 == sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&addr1, sizeof(addr1)))
    {
        perror("sendto() fail ");
    }
    else
    {
        printf("sendto() ok\n");
    }

    close(sock);//关闭套接字
    free(buf);

    return 0;
}


2.2 服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
/*
    server:addr1
    client:addr2
*/

//server
int main(int argc, char **argv)
{
    const int SIZE_BUF = 1024;
    char *buf = (char*)malloc(SIZE_BUF);
    struct sockaddr_in addr1, addr2;
    int sockfd = -1;

    if(argc != 3)
    {
        printf("Usage: %s port serverip\n", argv[0]);
        return 0;
    }

    bzero(&addr1, sizeof(addr1));                       //初始化结构体 addr1
    addr1.sin_family = AF_INET;                         //设置地址家族
    addr1.sin_port = htons(atoi(argv[1]));              //设置端口
    addr1.sin_addr.s_addr = inet_addr(argv[2]);  //设置地址
    int inet_aton1 = inet_aton(argv[2], &addr1.sin_addr);        //地址转换
    if(inet_aton1 == 0)        //如果转换失败
    {
        printf("server-ip is invalid.\n");
        return 1;
    }
    //服务器工作过程:
    // 1)创建数据报套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)//如果失败
    {
        //for linux, perror("xxx") show error description "yyy" in format xxx: yyy
        perror("socket() fail ");
        return 2;
    }
    // 2)绑定地址和端口信息,端口需要通知所有客户端,
    int bind1 = bind(sockfd, (struct sockaddr*)&addr1, sizeof(addr1));
    if(bind1 == -1)
    {
        perror("bind() fail ");
        return 3;
    }
    // 3)等待接收客户端数据

    // 4)收到数据后,按照通信规程(也可称为业务逻辑)处理,一般要反馈
    int len = sizeof(addr2);
    int recvfrom1 = recvfrom(sockfd,buf,SIZE_BUF,0,(struct sockaddr*)&addr2,&len);
    if(recvfrom1 == -1)
    {
        perror("recvfrom() fail ");
    }
    else
    {
        printf("recvfrom() ok\n");
    }
    // 5)在必要的情况下,关闭套接字
    close(sockfd);
    return 0;


}

3. 运行测试:

注意:先运行服务端代码,再运行客户端代码!!!

3.1 编译:

//服务端
gcc server2.c -o server2

//客户端
gcc client.c -o client

3.2 运行:

在文件所在位置右键打开终端,(注意要打开两个终端),进行如下操作:

//服务端
./server2 1234 127.0.0.1

//客户端
./client 1234 127.0.0.1

3.3 运行结果:

在这里插入图片描述
在这里插入图片描述

4. 踩坑经历:

4.1 问题:

第一次在终端输入的地址是192.168.0.1,程序始终无法运行,客户端可以发送数据包,服务端一直显示bind()失败。

后来才明白:默认情况下,登陆路由器管理界面的地址是192.168.0.1或192.168.1.1。即192.168.0.1已经和路由器的LAN端口进行绑定,所以会一直bind()失败。

4.2 解决办法:将IP地址换成127.0.0.1

127.0.0.1是回送地址,指本地机,一般用来测试使用。回送地址(127.x.x.x)是本机回送地址(Loopback Address),即主机IP堆栈内部的IP地址,主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,不进行任何网络传输。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值