socket基础知识以及各种使用场景

一.socket的基础知识

1.1socket的使用场景以及分类

socket本来也是用于本地进程间通信的,后来有了TCP/IP协议族的加入,才能实现跨主机通信。

socket是一个函数,我们可以指定参数告诉内核封装什么样的协议。socket是一种特殊的文件描述符 (everything in Unix is a file)并不仅限于TCP/IP协议,其他体系结构也会用到 socket。

套接字分为:

流式套接字(SOCK_STREAM):

TCP提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。

数据报套接字(SOCK_DGRAM)UDP:

提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。

原始套接字(SOCK_RAW)

可以对较低层次协议如IP、ICMP直接访问

二.学习socket需要了解的网络知识

2.1IP地址

ip地址是主机在网络中的编号,这个编号就是IP地址

2.1.1ip的分类:

IPV4 4字节(32bit)

IPV6 16字节 (128bit)

2.1.2ip地址的表示形式:

192.168.70.10 叫做点分十进制,是方便我们人类看的,是一个字符串类型。

而在计算中,ip地址就是一个无符号的4字节整型。

2.1.3为什么会有IPV4和IPV6之分?

是因为IPV4地址不够用了。

IPV4地址不够用了,不一定必须使用IPV6,因为现在有很多技术,都可以弥补IPV4数量不足的缺陷,如 NAT 技术:任意一个IP地址都可以经过路由器下发局域网IP地址,局域网内部通信,使用局域网的IP地址,如果数据要走出局域网会通过NAT技术,将数据包中的源IP地址从局域网IP替换成公网IP。

2.1.4IPV4的地址的组成:

由 网络号 和 主机号 组成。

2.1.5IPV4地址分类:

其中每个IP地址又可以通过路由器,下发局域网IP地址,

每类IP地址都有专门划分子网的保留段。

其中每个IP地址又可以通过路由器,下发局域网IP地址,

每类IP地址都有专门划分子网的保留段。

2.1.6IP的存储 :

既然是无符号4字节整型,就涉及字节序的问题。

在网络上字节序下,ip地址的存储:

 这个细节看主页文章字节序判定

2.1.7IP转换的一些函数以及使用:

IP地址转换的函数:

inet_addr()

将strptr所指的字符串转换成32位的网络字节序二进制值。

in_addr_t inet_addr(const char *strptr);

inet_ntoa()

将32位网络字节序二进制地址转换成点分十进制的字符串。

char *inet_ntoa(stuct in_addr inaddr);

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

int main(int argc, char const *argv[])
{
    unsigned char ip_str[]="192.168.60.45";
    unsigned int ip_int=0;

    ip_int=inet_addr(ip_str);

    unsigned char*p=(unsigned char*)&ip_int;

    printf("%d.%d.%d.%d\n",*p,*(p+1),*(p+2),*(p+3));

    return 0;
}

2.2子网掩码:

是由一堆连续的1和连续的0组成的。用来和IP地址取与运算来获取网络号的。从而能限制某一网段内能容纳的最大主机数。如,ip地址是192.168.70.8 子网掩码设置成 255.255.255.0取与运算可以得到的结果:192.168.70.0 ----这是网络号,网络号相同时,才能进行同这是,该网段内共有IP地址 256 个:其中 192.168.70.0 是网络号,是不能占用的192.168.70.255 是广播的地址,也不能占用网关设备也需要占用一个IP地址,一般是同一局域网内可用的编号最小的。

192.168.70.1

所以能容纳的主机数:256-1-1-1(网关设备也可以算作一台IP主机) = 253

2.2.1子网掩码应用实例:

192.168.70.x 网段将子网掩码设置成 255.255.255.128,同一子网能容纳的最大主机数?

答案:这种方式可以将同一网段再划分成两个子网

第一个子网IP地址范围:192.168.70.0~192.168.70.127

第二个子网IP地址范围:192.168.70.128~192.168.70.255

其中每个子网中有需要减掉 网络号 广播地址 网关地址

所以子网掩码不一定都是255255255.0 网关设备的IP地址也不一定是编号最小的。

2.3端口号:

虽然在linux系统中,进程号是用来标识唯一进程的,但是由于进程号是有操作系统分配的

且进程停止再运行时,不能保证进程号不变,所以使用进程号来标识进程就可能出现问题。

所以就发明了端口号来标识进程,端口号是人为可以自己指定的,为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别端口号的范围 0~65535 ,使用 unsigned short 存储也需要考虑字节序的问题端口号一般由IANA (Internet Assigned Numbers Authority) 管理众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)

已登记端口:1024~49151

动态或私有端口:49152~65535

常见的服务使用端口号:

FTP 21

SSH 22

TFTP 69

HTTP 80 8080

三.网络编程

3.1TCP网络编程

3.1.1流程

服务器:

创建流式套接字--socket()

填充服务器的网络信息结构体

将套接字和服务器的网络信息结构体绑定--bind()

将套接字设置成被动监听状态--listen()

阻塞等待客户端连接--accept()

数据收发--read write

关闭套接字--close()

客户端:

创建流式套接字--socket()

填充服务器的网络信息结构体

与服务器建立连接--connect()

数据收发--read write

关闭套接字--close()

3.1.2实例

用tcp实现通信的客户端(比较简单不说明)

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

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

#define N 128

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

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


    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 serveraddr_len = sizeof(serveraddr);

    char buff[N] = {0};
    int nbytes = 0;


    if(-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("connect error");
    }
    printf("与服务器连接成功...\n");

    while(1){
        memset(buff, 0, N);

        fgets(buff, N, stdin);
        buff[strlen(buff)-1] = '\0';

        if(-1 == send(sockfd, buff, N, 0)){
            ERRLOG("send error");
        }
        if(!strncmp(buff, "quit", 5)){
            break;
        }

        if(-1 == recv(sockfd, buff, N, 0)){
            ERRLOG("recv error");
        }

        printf("应答:[%s]\n", buff);

    }

    close(sockfd);

    return 0;
}


用tcp实现通信的客户端(比较简单不说明)

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

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

#define N 128

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

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

    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 serveraddr_len = sizeof(serveraddr);


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


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


    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);

    char buff[N] = {0};
    int nbytes = 0;

    int acceptfd = 0;
    while(1){
        printf("开始等待客户端连接..\n");
        if(-1 == (acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len))){
            ERRLOG("acceptfd error");
        }
        printf("客户端 [%s:%d] 连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

        while(1){
            memset(buff, 0, N);
        
            if(-1 == (nbytes = recv(acceptfd, buff, 128, 0))){
                ERRLOG("recv error");
            }else if(0 == nbytes){
                printf("[%s:%d]:断开了连接...\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
                break;
            }
            if(!strncmp(buff, "quit", 5)){
                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, "--hqyj");
 
            if(-1 == send(acceptfd, buff, N, 0)){
                ERRLOG("send error");
            }
        }

        close(acceptfd);
    }

   
    close(sockfd);

    return 0;
}

3.1.3 练习:实现客户端下载服务器所在目录的文件功能

要求:

1.客户端和服务器不要在同一个路径执行

2.客户端先给服务器发送要下载的文件名

3.服务器收到文件名后,判断当前路径下是否有该文件

open(只读) 返回-1 且错误码 是2号 说明文件不存在

如果不存在,则回复给客户端文件不存在

如果存在,也要给客户端回复文件存在的消息

4.客户端收到服务的回复 如果不存在 则重新输入文件名

如果存在 则新建并打开文件 写|创建|清空

5.开始发送文件内容

6.文件结束发送 "****over*****" 给客户端

7.客户端收到 "****over****" 后就不要在recv 了

8.客户端退出 服务器等待下一次客户端的连接

服务器代码

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

#define N 512
typedef struct MSG_{
    int num;
    char buff[N];
}msg_t;

int send_file(int acceptfd,const char *filename)
{
    int fd;
    int ret=0;
    msg_t tid;

    if((fd=open(filename,O_RDONLY))==-1){
        ERRLOG("open filename error");
    }

    while((ret=read(fd,tid.buff,sizeof(tid.buff)))>0){
        tid.num=ret;
        if(send(acceptfd,&tid,sizeof(tid),0)==-1){
            ERRLOG("send error1");
        }
        memset(&tid,0,sizeof(tid));
    }

    memset(&tid,0,sizeof(tid));
    strcat(tid.buff,"over");
    if(send(acceptfd,&tid,sizeof(tid),0)==-1){
        ERRLOG("send error2");
    }
    return 0;
}


int is_file(msg_t *msg)//判断文件是否存在
{
    if(msg==NULL){
        ERRLOG("The ginseng error");
    }

    int fd;

    if((fd=open(msg->buff,O_RDONLY))==-1){
        if(errno==2){
            msg->num=2;
            return -1;
        }
        ERRLOG("open file error");
    }

    return 0;
}

int main(int argc, char const *argv[])
{
    int sockfd=0;
    int acceptfd=0;
    msg_t msg;

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

    //网络信息结构体
    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 serveraddr_len=sizeof(serveraddr);
    
    //将网络信息结构体进行绑定
    if(bind(sockfd,(struct sockaddr*)&serveraddr,serveraddr_len)==-1){
        ERRLOG("bind error");
    }

    //将套接字设置为被动监听状态
    if(listen(sockfd,5)==-1){
        ERRLOG("listen error");
    }

    //设置一个新的网络信息结构体存储客户端信息
    struct sockaddr_in clientaddr;
    memset(&clientaddr,0,sizeof(clientaddr));
    socklen_t clientaddr_len=sizeof(clientaddr);

    if((acceptfd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len))==-1){
        ERRLOG("accepfd error");
    }

    printf("客户[%s:%d]连接成功\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

    while(1){

        //接受客户端发来的文件名
        memset(&msg,0,sizeof(msg));
        if(recv(acceptfd,&msg,sizeof(msg),0)==-1){
            ERRLOG("recv error3");
        }

        //验证是否有这个文件//当前目录
        is_file(&msg);
    
        //把是否有这个文件的信息发送给客户端
        if(send(acceptfd,&msg,sizeof(msg),0)==-1){
            ERRLOG("acceptfd error");
        }

        if(msg.num==2){
            continue;
        }else{
            send_file(acceptfd,msg.buff);           
        }

    }

    close(acceptfd);
    close(sockfd);
      return 0;
}

客户端代码

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

//这里设置这个结构体主要是为了防止防沾包

#define N 512
typedef struct MSG_{
    int num;
    char buff[N];
}msg_t;

int jieshou_file(int sockfd,const char *filename)
{
    int fd;
    int ret=0;
    msg_t tid;
    if((fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666))==-1){
        ERRLOG("open error3");
    }

    while(1){
        memset(&tid,0,sizeof(tid));
        if((ret=recv(sockfd,&tid,sizeof(tid),0))==-1){
            ERRLOG("recv error");
        }


        if(!strncmp(tid.buff,"over",5)){
            break;
        }

        write(fd,tid.buff,strlen(tid.buff));
    }

    return 0;
}


int main(int argc, char const *argv[])
{
    int sockfd=0;
    msg_t msg;

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

    struct sockaddr_in clientaddr;
    clientaddr.sin_family=AF_INET;
    clientaddr.sin_port=htons(atoi(argv[2]));
    clientaddr.sin_addr.s_addr=inet_addr(argv[1]);
    socklen_t clientaddr_len=sizeof(clientaddr);

    if(connect(sockfd,(struct sockaddr*)&clientaddr,clientaddr_len)==-1){
        ERRLOG("connect error");
    }

    printf("connect successfully\n");
    
    while(1){
        
        //输入你想要下载文件的姓名
        memset(&msg,0,sizeof(msg));
        printf("Please enter the file name  you want to downloadn >");
        fgets(msg.buff,sizeof(msg.buff),stdin);
        msg.buff[strlen(msg.buff)-1]='\0';

        //将文件名字发送给服务器
        if(send(sockfd,&msg,sizeof(msg),0)==-1){
            ERRLOG("Failed to send file name to server");
        }

        //接受服务器反回来的消息
        memset(&msg,0,sizeof(msg));
        if(recv(sockfd,&msg,sizeof(msg),0)==-1){
            ERRLOG("recv error1");
        }

        if(msg.num==2){
            printf("There is no such file as you typed\n");
            continue;
        }else{            
            jieshou_file(sockfd,msg.buff);
            break;
        }
        
    }


    close(sockfd);
    return 0;
}

3.1.3代码解释说明

这个代码并不是我们常用的下载方式,一般我们会先获取要下载文件的长度然后进行下载,并不会像这样最后再发一个over来判断客户端接受方结束,之所以写这个练习是为了来弄清楚什么是沾包问题。

TCP的粘包问题

TCP的底层有一个nagel算法 会将一定短的时间内的发往同一个接收端的 多个小的数据包组成一个整体 一起发送 而接收端无法区分数据的类型 所以就可能有冲突

解决方法一:是用sleep来将发送的东西分开,但是这样实际运用中并不靠谱并且实际传输中还有命令传输总不能发一个加一个sleep,而且sleep会是运行效率变慢所以这个方法实际开发中并不使用

解决方法二:就是将要发送的各种文件以及命令全部以结构体的方式打包发出,这样就不会出现沾包问题

3.1.4简单的TCP协议拆分

1.4 练习:使用代码控制机械臂

机械臂程序 使用 QT 写的一个windows下运行的 TCP 服务器

运行在哪个主机上 ip地址就是哪个主机的IP地址 端口号就是自己指定的 控制端口号

点击开启监听 就ok了 就开始等待客户端连接了

机械臂的协议:0xff 0x02 (1) (2) 0xff

(1) 要操作的摆臂 0x00 红色的摆臂 0x01 控制蓝色的摆臂

(2) 要偏移的角度

红 [-90, 90] char

蓝 [0, 180] unsigned char

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

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

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

    //创建流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    //网络字节序的端口号 8888  9999  6789 等 都可以
    serveraddr.sin_port = htons(atoi(argv[2]));
    //网络字节序的IP地址,IP地址不能乱填
    //自己的主机ifconfig 查到的ip地址是多少就填多少
    //如果本机测试使用 也可以填写 127.0.0.1
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    socklen_t serveraddr_len = sizeof(serveraddr);

    //与服务器建立连接
    if(-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("connect error");
    }
    printf("与服务器连接成功...\n");


    char red_buff[5] = {0xff, 0x02, 0x00, 0x00, 0xff};
    unsigned char blue_buff[5] = {0xff, 0x02, 0x01, 0x00, 0xff};

    //将机械臂进行初始化----起始位置归零
    if(-1 == send(sockfd, red_buff, 5, 0)){
        ERRLOG("send error");
    }
    sleep(1);
    if(-1 == send(sockfd, blue_buff, 5, 0)){
        ERRLOG("send error");
    }

    char choose = 0;
    while(1){
        choose = getchar();
        getchar();//清理垃圾字符
        switch(choose){
            case 'w':
                if(blue_buff[3] < 180){
                    blue_buff[3]+=5;
                }
                if(-1 == send(sockfd, blue_buff, 5, 0)){
                    ERRLOG("send error");
                }
                break;
            case 's':
                if(blue_buff[3] > 0){
                    blue_buff[3]-=5;
                }
                if(-1 == send(sockfd, blue_buff, 5, 0)){
                    ERRLOG("send error");
                }
                break;
            case 'a':
                if(red_buff[3] > -90){
                    red_buff[3]-=5;
                }
                if(-1 == send(sockfd, red_buff, 5, 0)){
                    ERRLOG("send error");
                }
                break;
            case 'd':
                if(red_buff[3] < 90){
                    red_buff[3]+=5;
                }
                if(-1 == send(sockfd, red_buff, 5, 0)){
                    ERRLOG("send error");
                }
                break;
        }
    }

    //关闭套接字
    close(sockfd);

    return 0;
}

3.1.5tcp的并发

3.1.5.1使用多线程实现TCP并发服务器

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

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

#define N 128

typedef struct _MSG{
    int acceptfd;
    struct sockaddr_in clientaddr;
}msg_t;

void *deal_read_write(void *arg){
    msg_t msg = *(msg_t *)arg;
    char buff[N] = {0};
    int nbytes = 0;
    printf("客户端 [%s:%d] 连接了\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
    
    //收发数据
    while(1){
        memset(buff, 0, N);
        //接受数据
        if(-1 == (nbytes = recv(msg.acceptfd, buff, 128, 0))){
            perror("recv error");
            break;
        }else if(0 == nbytes){
            printf("[%s:%d]:断开了连接...\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
            break;
        }
        if(!strncmp(buff, "quit", 5)){
            printf("[%s:%d]:退出了...\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
            break;
        }
        //打印数据
        printf("[%s:%d]:[%s]\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port), buff);
        //组装应答
        strcat(buff, "--hqyj");
        //发送应答
        if(-1 == send(msg.acceptfd, buff, N, 0)){
            ERRLOG("send error");
        }
    }
    close(msg.acceptfd);
    pthread_exit(NULL);
}

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

    //创建流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    //网络字节序的端口号 8888  9999  6789 等 都可以
    serveraddr.sin_port = htons(atoi(argv[2]));
    //网络字节序的IP地址,IP地址不能乱填
    //自己的主机ifconfig 查到的ip地址是多少就填多少
    //如果本机测试使用 也可以填写 127.0.0.1
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    socklen_t serveraddr_len = sizeof(serveraddr);

    //将套接字和网络信息结构体绑定
    if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("bind error");
    }

    //将套接字设置成被动监听状态
    if(-1 == listen(sockfd, 5)){
        ERRLOG("listen error");
    }

    //定义一个保存客户端信息的结构体
    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);

    msg_t msg;
    pthread_t tid = 0;

    //阻塞等待客户端连接
    int acceptfd = 0;
    while(1){
        if(-1 == (acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len))){
            ERRLOG("acceptfd error");
        }
        memset(&msg, 0, sizeof(msg));
        msg.clientaddr = clientaddr;
        msg.acceptfd = acceptfd;
        //有客户端连接就创建线程专门处理读写
        if(0 != pthread_create(&tid, NULL, deal_read_write, (void *)&msg)){
            ERRLOG("pthread_create error");
        }

        //设置线程分离属性 由操作系统回收线程的资源
        pthread_detach(tid);
    }

    close(sockfd);

    return 0;
}

3.5.1.2tcp的多进程并发

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

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

#define N 128

//信号处理函数
void function(int x){
    wait(NULL);
}

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

    //创建流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    //网络字节序的端口号 8888  9999  6789 等 都可以
    serveraddr.sin_port = htons(atoi(argv[2]));
    //网络字节序的IP地址,IP地址不能乱填
    //自己的主机ifconfig 查到的ip地址是多少就填多少
    //如果本机测试使用 也可以填写 127.0.0.1
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    socklen_t serveraddr_len = sizeof(serveraddr);

    //将套接字和网络信息结构体绑定
    if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("bind error");
    }

    //将套接字设置成被动监听状态
    if(-1 == listen(sockfd, 5)){
        ERRLOG("listen error");
    }

    //定义一个保存客户端信息的结构体
    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);

    char buff[N] = {0};
    int nbytes = 0;
    pid_t pid = 0;

    signal(SIGUSR1, function);

    //阻塞等待客户端连接
    int acceptfd = 0;
    while(1){
        if(-1 == (acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len))){
            ERRLOG("acceptfd error");
        }
        //有客户端连接就创建子进程来处理读写
        if(-1 == (pid = fork())){
            ERRLOG("fork error");
        }else if(0 == pid){//子进程
            printf("客户端 [%s:%d] 连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            //收发数据
            while(1){
                memset(buff, 0, N);
                //接受数据
                if(-1 == (nbytes = recv(acceptfd, buff, 128, 0))){
                    ERRLOG("recv error");
                }else if(0 == nbytes){
                    printf("[%s:%d]:断开了连接...\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
                    break;
                }
                if(!strncmp(buff, "quit", 5)){
                    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, "--hqyj");
                //发送应答
                if(-1 == send(acceptfd, buff, N, 0)){
                    ERRLOG("send error");
                }
            }
            //关闭套接字
            close(acceptfd);
            //给父进程发一个信号
            kill(getppid(), SIGUSR1);
            //子进程退出
            exit(0);
        }else if(0 < pid){//父进程
            close(acceptfd);
        }
    }

    close(sockfd);

    return 0;
}

3.5.1.3使用select实现tcp多进程通信

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

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

#define N 128

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

    //创建流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    //网络字节序的端口号 8888  9999  6789 等 都可以
    serveraddr.sin_port = htons(atoi(argv[2]));
    //网络字节序的IP地址,IP地址不能乱填
    //自己的主机ifconfig 查到的ip地址是多少就填多少
    //如果本机测试使用 也可以填写 127.0.0.1
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    socklen_t serveraddr_len = sizeof(serveraddr);

    //将套接字和网络信息结构体绑定
    if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("bind error");
    }

    //将套接字设置成被动监听状态
    if(-1 == listen(sockfd, 5)){
        ERRLOG("listen error");
    }

    int maxfd = 0;
    //定义要监视的集合
    fd_set readfds;
    fd_set readfds_temp;
    FD_ZERO(&readfds);
    FD_ZERO(&readfds_temp);

    //将sockfd添加到集合中
    FD_SET(sockfd, &readfds);
    maxfd = maxfd>sockfd?maxfd:sockfd;

    char buff[N] = {0};
    int nbytes = 0;
    int acceptfd = 0;
    int ret = 0;
    int i = 0;

    //定义超时时间
    struct timeval tm;
    tm.tv_sec = 5;
    tm.tv_usec = 0;

    while(1){
        readfds_temp = readfds;
        //每次需要重置超时时间
        tm.tv_sec = 5;
        tm.tv_usec = 0;

        if(-1 == (ret = select(maxfd+1, &readfds_temp, NULL, NULL, &tm))){
            ERRLOG("select error");
        }else if(0 == ret){
            //此处可以写超时的处理  不同的业务处理方式是不一样的
            printf("timeout...\n");
        }else{
            for(i = 3; i < maxfd+1 && ret!=0; i++){
                if(FD_ISSET(i, &readfds_temp)){
                    if(i == sockfd){
                        //说明有新客户端连接到服务器了
                        if(-1 == (acceptfd = accept(i, NULL, NULL))){
                            ERRLOG("accept error");
                        }
                        printf("客户端[%d]连接到服务器..\n", acceptfd);
                        //将新的 acceptfd 添加到集合中
                        FD_SET(acceptfd, &readfds);
                        maxfd = maxfd>acceptfd?maxfd:acceptfd;
                    }else{
                        memset(buff, 0, N);
                        //接受数据
                        if(-1 == (nbytes = recv(i, buff, 128, 0))){
                            ERRLOG("recv error");
                        }else if(0 == nbytes){
                            printf("客户端[%d]断开了连接..\n", i);
                            //将其在集合中删除
                            FD_CLR(i, &readfds);
                            //关闭文件描述符
                            close(i);
                            continue;
                        }
                        if(!strncmp(buff, "quit", 5)){
                            printf("客户端[%d]退出了..\n", i);
                            //将其在集合中删除
                            FD_CLR(i, &readfds);
                            //关闭文件描述符
                            close(i);
                            continue;
                        }
                        //打印数据
                        printf("客户端[%d]发来数据[%s]\n", i, buff);
                        //组装应答
                        strcat(buff, "--hqyj");
                        //发送应答
                        if(-1 == send(i, buff, N, 0)){
                            ERRLOG("send error");
                        }
                    }
                    ret--;
                }
            }
        }
    }

    close(sockfd);

    return 0;
}

3.5.1.4用poll实现tcp并发

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

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

#define N 128

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

    //创建流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    //网络字节序的端口号 8888  9999  6789 等 都可以
    serveraddr.sin_port = htons(atoi(argv[2]));
    //网络字节序的IP地址,IP地址不能乱填
    //自己的主机ifconfig 查到的ip地址是多少就填多少
    //如果本机测试使用 也可以填写 127.0.0.1
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    socklen_t serveraddr_len = sizeof(serveraddr);

    //将套接字和网络信息结构体绑定
    if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("bind error");
    }

    //将套接字设置成被动监听状态
    if(-1 == listen(sockfd, 5)){
        ERRLOG("listen error");
    }

    int maxfd = 0;
    //创建要监视的文件描述符集合
    struct pollfd my_fds[1024];//使用 poll 数组长度允许超过1024
    //将所有文件描述符先置成 -1
    int i = 0;
    int j = 0;
    for(i = 0; i < 1024; i++){
        my_fds[i].fd = -1;
    }

    //将sockfd添加到集合中
    my_fds[0].fd = sockfd;
    my_fds[0].events = POLLIN;//正常应该清空 events 并且用 |= 的方式赋值
    maxfd = maxfd>sockfd?maxfd:sockfd;

    char buff[N] = {0};
    int nbytes = 0;

    int acceptfd = 0;
    int ret = 0;
    while(1){
        if(-1 == (ret = poll(my_fds, maxfd, 5000))){
            ERRLOG("poll error");
        }else if(0 == ret){
            printf("timeout..\n");
        }else{
          for(i = 0; i <= maxfd && ret!=0 ; i++){
                if(my_fds[i].revents & POLLIN != 0){
                    //说明第i位的fd就绪了
                    if(my_fds[i].fd == sockfd){
                        //处理连接请求
                        if(-1 == (acceptfd = accept(my_fds[i].fd, NULL, NULL))){
                            ERRLOG("accept error");
                        }
                        printf("客户端[%d]连接到服务器..\n", acceptfd);
                        //将acceptfd添加到集合中
                        //遍历数组 给 acceptfd找个位置
                        for(j = 0; j < 1024; j++){
                            if(my_fds[j].fd == -1){
                                my_fds[j].fd = acceptfd;
                                my_fds[j].events = POLLIN;
                                maxfd = maxfd>acceptfd?maxfd:acceptfd;
                                break;
                            }
                        }
                        if(j == 1024){
                            printf("满了\n");
                            close(acceptfd);
                        }
                    }else{
                        memset(buff, 0, N);
                        //接受数据
                        if(-1 == (nbytes = recv(my_fds[i].fd, buff, 128, 0))){
                            ERRLOG("recv error");
                        }else if(0 == nbytes){
                            printf("客户端[%d]断开了连接...\n", my_fds[i].fd);
                            //将其在数组中删除
                            close(my_fds[i].fd);
                            my_fds[i].fd = -1;
                            continue;
                        }
                        if(!strncmp(buff, "quit", 5)){
                            printf("客户端[%d]退出了...\n", my_fds[i].fd);
                            //将其在数组中删除
                            close(my_fds[i].fd);
                            my_fds[i].fd = -1;
                            continue;
                        }
                        //打印数据
                        printf("客户端[%d]发来数据[%s]\n", my_fds[i].fd, buff);
                        //组装应答
                        strcat(buff, "--hqyj");
                        //发送应答
                        if(-1 == send(my_fds[i].fd, buff, N, 0)){
                            ERRLOG("send error");
                        }
                    }
                    ret--;
                }
            }
        }
    }

    close(sockfd);

    return 0;
}

3.2UDP网络编程

3.2.1流程

服务器流程:

创建用户数据报套接字

填充服务器的网络信息结构体

绑定套接字与服务器网络信息结构体

收发数据

关闭套接字

客户端流程:

创建用户数据报套接字

填充服务器的网络信息结构体

收发数据

关闭套接字

3.2.2udp使用实例

服务器代码

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

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

#define N 128

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

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

    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 serveraddr_len = sizeof(serveraddr);

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

    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);

    char buff[N] = {0};

    while(1){
        memset(buff, 0, N);
        if(-1 == recvfrom(sockfd, buff, N, 0, (struct sockaddr *)&clientaddr, &clientaddr_len)){
            ERRLOG("recvfrom error");
        }
        printf("[%s:%d]:[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff);

        strcat(buff, "--hqyj");
        if(-1 == sendto(sockfd, buff, N, 0, (struct sockaddr *)&clientaddr, clientaddr_len)){
            ERRLOG("sendto error");
        }
    }

    close(sockfd);

    return 0;
}

客户端代码

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

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

#define N 128

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

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


    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 serveraddr_len = sizeof(serveraddr);

    char buff[N] = {0};

    while(1){
        memset(buff, 0, N);

        fgets(buff, N, stdin);
        buff[strlen(buff)-1] = '\0';//清空结尾的 '\n'
    
        if(-1 == sendto(sockfd, buff, N, 0, (struct sockaddr *)&serveraddr, serveraddr_len)){
            ERRLOG("send error");
        }
        memset(buff, 0, N);

        if(-1 == recvfrom(sockfd, buff, N, 0, NULL, NULL)){
            ERRLOG("recv error");

        printf("应答:[%s]\n", buff);

    close(sockfd);

    return 0;
}

3.2.3TFTP协议分析

 

在TFTP服务器上下载文件。

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

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

int main(int argc, const char *argv[]){
    if(3 != argc){
        printf("Usage: %s <IP> <port>\n", argv[0]);
        return -1;
    }
    int fd = 0;
    int nbytes = 0;
    unsigned char buff[600] = {0};
    unsigned short code = 0;//操作码
    unsigned short number = 0;//块编号  错误码
    unsigned short block_num = 0;//客户端用来校验收到的包的编号的
    char text[512] = {0};//文件内容  差错信息

    char filename[32] = {0};
    //创建用户数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    //网络字节序的端口号 69
LOOP:   //如果失败了 重新输入文件名时,因为serveraddr中的69端口号已经被
        //临时端口覆盖了  所以需要重新赋值 69
    serveraddr.sin_port = htons(atoi(argv[2]));
    //网络字节序的IP地址,TFTP 服务器的ip地址
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    socklen_t serveraddr_len = sizeof(serveraddr);

    printf("请输入文件名:");
    fgets(filename, 32, stdin);
    filename[strlen(filename)-1] = '\0';

    //组装协议的方式
    //方式1  一个字节一个字节的赋值  注意:是网络字节序的
    //buff[0] = 0;
    //buff[1] = 1;

    //方式2:
    //*(unsigned short *)buff = htons(1);

    //方式3:
    nbytes = sprintf(buff, "%c%c%s%c%s%c", 0, 1, filename, 0, "octet", 0);

    //给服务器发送请求
    if(-1 == sendto(sockfd, buff, nbytes, 0, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("sendto error");
    }
    while(1){
        //接收服务器返回的数据
        if(-1 == (nbytes = recvfrom(sockfd, buff, 600, 0, (struct sockaddr *)&serveraddr, &serveraddr_len))){
            ERRLOG("recvfrom error");
        }
        //解析操作码
        code = ntohs(*(unsigned short *)buff);
        //解析块编号 或者 错误码
        number = ntohs(*(unsigned short *)(buff+2));
        //解析 文件内容 或者 差错信息
        strncpy(text, buff+4, nbytes-4);

        if(3 == code && number == block_num+1){//说明是文件内容
            block_num++;//校验的块编号  +1
            if(1 == number){
                //只有第一次收到文件内容才要打开文件
                if(-1 == (fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0664))){
                    ERRLOG("open error");
                }
            }
            //将内容写入文件里
            write(fd, text, nbytes-4);

            //printf("code = [%d]  block_num = [%d]  text = [%s]\n", code, number, text);

            //组装应答
            *(unsigned short *)buff = htons(4);
            *(unsigned short *)(buff+2) = htons(number);
            //发送应答
            if(-1 == sendto(sockfd, buff, 4, 0, (struct sockaddr *)&serveraddr, serveraddr_len)){
                ERRLOG("sendto error");
            }
            //文件发送完成
            if(nbytes < 512){
                close(fd);
                break;
            }
        }else if(5 == code){//出错了
            printf("errcode = [%d]   errstr = [%s]\n", number, text);
            goto LOOP;
        }
    }

    //关闭套接字
    close(sockfd);

    return 0;
}

这个代码主要就是根据协议给服务器发消息,然后实现文件下载的功能,也是我们必须得掌握好得一个知识点。

3.2.4用udp实现聊天室

头文件

#ifndef __MYHEAD_H__
#define __MYHEAD_H__

#include <head.h>
#define N 512
//聊天操作用的结构体
typedef struct _MSG{
    char ch;//用来'l'聊天,'q'退出,'登录d'
    char name[128];//存名字
    char text[N];//存聊天内容
}msg_t;
//用来保存每个用户信息的结构体
typedef struct _Jilu{
    struct sockaddr_in addr;
    struct _Jilu *next;
}jilu_t;


int create_head(jilu_t **head);
int input_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len);
int wx_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len);
int tuichu_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len);
#endif

服务器

#include "myhead.h"

int main(int argc, char const *argv[])
{
    int sockfd=0;
    pid_t pid=0;
    msg_t msg;//用来进行各种操作
    msg_t faso;//用来发系统消息

    if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
        ERRLOG("创建服务器套接字失败");
    }

    //将网络信息结构体放入服务器中
    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 serveraddr_len=sizeof(serveraddr);

    //将套接字与网络信息结构体绑定
    if(bind(sockfd,(struct sockaddr*)&serveraddr,serveraddr_len)==-1){
        ERRLOG("将套接字与网络信息结构体绑定失败");
    }

    //创建一个新的网络信息结构体来存客户端的信息
    struct sockaddr_in clientaddr;
    clientaddr.sin_family=AF_INET;
    socklen_t clientaddr_len=sizeof(clientaddr);
    
    //创建进程
    pid=fork();
    if(pid==-1){
        ERRLOG("服务器创建进程失败");
    }else if(pid==0){
        //创建一个单列表保存网络信息结构体
        jilu_t *head;
        create_head(&head);
        memset(&msg,0,sizeof(msg));
        while(1){
            if(recvfrom(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&clientaddr,&clientaddr_len)==-1){
                ERRLOG("接受客户端传来的信息失败");
            }          
             switch(msg.ch){
                case 'd'://登录信息
                    input_addr(head,msg,sockfd,clientaddr,clientaddr_len);
                    //head->next=NULL; //这个用来测试用的                  
                    break;
                case 'l'://聊天信息
                    wx_addr(head,msg,sockfd,clientaddr,clientaddr_len);
                    break;
                case 'q'://退出信息
                    tuichu_addr(head,msg,sockfd,clientaddr,clientaddr_len);
                    break;
            }
        }
    }else{
        while(1){
            //发系统消息
            memset(&faso,0,sizeof(faso));
            fgets(faso.text,sizeof(faso.text),stdin);
            faso.text[strlen(faso.text)-1]='\0';
            faso.ch='l';
            sprintf(faso.name,"%s","系统消息");

            if(sendto(sockfd,&faso,sizeof(faso),0,(struct sockaddr*)&serveraddr,serveraddr_len)==-1){
                ERRLOG("发送系统消息失败");
            }
        }
    }
    return 0;
}

客户端

#include "myhead.h"


int main(int argc, char const *argv[])
{

    //判断输入的对不对
    if(argc!=3){
        printf("输入格式错误,./a.out ip port\n");
        exit(EXIT_SUCCESS);
    }

    int sockfd=0;
    pid_t pid=0;
    msg_t msg;//创建发送用户的信息
    

    if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
        ERRLOG("创建客户端套接字失败");
    }

    //将客户端网络信息结构体进行绑定
    struct sockaddr_in clientaddr;
    memset(&clientaddr,0,sizeof(clientaddr));
    clientaddr.sin_family=AF_INET;
    clientaddr.sin_port=htons(atoi(argv[2]));
    clientaddr.sin_addr.s_addr=inet_addr(argv[1]);
    socklen_t clientaddr_len=sizeof(clientaddr);

    //输入用户的姓名进行登录操作
    msg.ch='d';
    printf("请输入你用来登录的姓名");
    fgets(msg.name,sizeof(msg.name),stdin);
    msg.name[strlen(msg.name)-1]='\0';

    //给服务器发送用户已经登录上的操作
    if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&clientaddr,clientaddr_len)==-1){
        ERRLOG("客户端给服务器发送的登录信息失败");
    }
    
    //创建进程,子进程用来接受,父进程用来发送
    pid=fork();
    if(pid==-1){
        ERRLOG("客户端创建进程失败");
    }else if(pid==0){
        //用来接受服务器发来的消息
        while(1){
            memset(&msg,0,sizeof(msg));
            if(recvfrom(sockfd,&msg,sizeof(msg),0,NULL,NULL)==-1){
                ERRLOG("接受服务器发来的信息错误");
            }

            printf("[%s]>>(%s)\n",msg.name,msg.text);
        }
    }else{
        //写入要判聊天的内容
        while(1){
            memset(msg.text,0,sizeof(msg.text));
            fgets(msg.text,sizeof(msg.text),stdin);
            msg.text[strlen(msg.text)-1]='\0';

            if(strncmp("quit",msg.text,5)==0){
                msg.ch='q';
            }else{
                msg.ch='l';
            }
            
            //将写好的内容发送给服务器
            if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&clientaddr,clientaddr_len)==-1){
                ERRLOG("将聊天内容发送给服务器失败");
            }

            //当识别到停止的时候,关闭进程
            if(strncmp("quit",msg.text,5)==0){
                kill(pid,SIGKILL);
                close(sockfd);
                exit(EXIT_SUCCESS);
            }
        }
    }   

    return 0;
}

函数

#include "myhead.h"

//创建一个单链表头
int create_head(jilu_t **head)
{   
    if(head==NULL){
        printf("传送错误,请检查\n");
        return -1;
    }

    (*head)=(jilu_t *)malloc(sizeof(jilu_t));
    (*head)->next=NULL;

    return 0;

}

//记录登录的用户的信息
int input_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len)
{
    if(head==NULL){
        printf("传送错误,请检查\n");
        return -1;
    }

    //将这个用户登录的信息发送给所有人
    snprintf(msg.text,sizeof(msg.text),"[%s]%s",msg.name,"登录了");
    //这个用来记录头的地址
    jilu_t *jilu_head=head;

    while(jilu_head->next!=NULL){
        jilu_head=jilu_head->next;
        if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&jilu_head->addr,clientaddr_len)==-1){
            ERRLOG("将用户登录信息发给所有人失败");
        }
    }
    //创建一个新的节点,并且把新的用户信息放入新得单列表
    jilu_t *temp=NULL;
    create_head(&temp);
    temp->addr=clientaddr;

    //用头插法将用户信息插入链表
    temp->next=head->next;
    head->next=temp;

    return 0;
}
 
int wx_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len)
{
    
    if(head==NULL){
        printf("传送错误,请检查\n");
        return -1;
    }

    //将接受到的消息发给除了自己以外的所有人
    jilu_t *jilu_head=head;
    while(jilu_head->next!=NULL){
        jilu_head=jilu_head->next;
        if(0!=memcmp(&(jilu_head->addr),&clientaddr,sizeof(clientaddr))){     
            if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&jilu_head->addr,clientaddr_len)==-1){
                ERRLOG("将聊天内容发给所有人失败");
            }
        }
    }

    return 0;
}
int tuichu_addr(jilu_t *head,msg_t msg,int sockfd,struct sockaddr_in clientaddr,socklen_t clientaddr_len)
{
    if(head==NULL){
        printf("传送错误,请检查\n");
        return -1;
    }

    snprintf(msg.text,sizeof(msg.text),"%s%s",msg.name,"退出登录");

    jilu_t *jilu_head=head;
    jilu_t *pdel=NULL;
    
    while(jilu_head->next!=NULL){
        
        if(0==memcmp(&(jilu_head->next->addr),&clientaddr,sizeof(clientaddr))){
            pdel=jilu_head->next;
            jilu_head->next=pdel->next;
            free(pdel);
            pdel=NULL;
        }else{
            jilu_head=jilu_head->next;
            if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&jilu_head->addr,clientaddr_len)==-1){
                ERRLOG("将这个退出的信息告诉所有人失败");
            }
        }
    }

   
    return 0;
}

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值