c语言实现socket编程--客户端与客户端之间通过服务器进行通讯_c语言socket服务端-程序员宅基地

socket编程:
服务端可以分为:socket()、bind()、listen()、accept() 剩下就可以处理数据
客户端有:socket()、connect()、处理数据
socket(): 创建一个描述符用来唯一标识一个socket。
  int socket(int domain, int type, int protocol);
  domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
  type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW(原始头报文)、 SOCK_PACKET、SOCK_SEQPACKET等等。
 protocol:故名思义,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
 protocol为0时,会自动选择type类型对应的默认协议 失败是 返回值为-1
 当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。
 如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
 eg:int client_socket = socket(AF_INET, SOCK_STREAM, 0);
bind()函数: 函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
 sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
 addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
 addrlem:地址长度。
 bind()函数的返回值为0时表示绑定成功,-1表示绑定失败,errno的错误值如表1所示。
 eg:int ret = bind(listen_socket, (struct sockaddr *)&service_addr, sizeof(service_addr)) //前面已经定义过结构体了struct sockaddr_in service_addr; memset(service_addr, 0, sizeof(service_addr)); //初始化service_addr
listen()、connect()函数: 如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时
 调用connect()发出连接请求,服务器端就会接收到这个请求。
 int listen(int sockfd, int backlog);
 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
 listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。若成功则为0,若出错则为-1
 connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用
 connect函数来建立与TCP服务器的连接。 如果请求连接成功,则返回0,否则返回-1 包含错误码。
eg: #define BACKLOG 5
 int ret = listen(listen_socket, BACKLOG)
 int listen_socket = connect(client_socket, (struct sockaddr *)&addr, addrlen)
accept()函数: TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  第一个参数为服务器的socket描述字
  第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址
  第三个参数为协议地址的长度。
  如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接(仅存在本次服务)传给accept的原始套接字没有关联到这个连接,而是继续保持可用状态并接收其他连接请求accept返回的句柄建立的连接包括四部分:源IP、源端口号、目的IP、目的端口号
eg:accept(listen_socket, (struct sockaddr *)&client_addr, &addrlen) //&表示取地址 *才是取值
linux不区分套接字文件以及普通文件:
read()、write()函数
 ssize_t write(int fd, const void *buf, size_t nbytes);
 ssize_t read(int fd, void *buf, size_t count);
 write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节 数。失败时返回-1,并设置errno变量。在网络程序中,当我们向套接字文件描述符写时有俩种可能。write的返回值大于0,表示写了部分或者是 全部的数据。返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示 网络连接出现了问题(对方已经关闭了连接)。
 返回值:如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。
 read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。
 返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。
eg:
 int ret = read(client_socket, buf, SIZE - 1);
 write(client_socket, buf, strlen(buf));
Windows区分普通文件和套接字:
send()、recv()函数:
 int send(SOCKET sock, const char *buf, int len, int flags);
 int recv(SOCKET sock, char *buf, int len, int flags);
 sock 为要发送数据的套接字,buf 为要发送的数据的缓冲区地址,len 为要发送的数据的字节数,flags 为发送数据时的选项。
eg:
 send(new_fd,“Hello World!”,12,0);
 recv(sockfd,buf,MAX_DATA,0);

close()函数: 在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完
打开的文件要调用fclose关闭打开的文件。
 int close(int fd);
eg:close(client_socket);
 close(listen_socket);

struct sockaddr和struct sockaddr_in 这两个结构体用来处理网络通信的地址
 sockaddr在头文件#include <sys/socket.h>中定义sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了
 sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中

			struct sockaddr
			{  
				sa_family_t sin_family;//地址族
    			char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   		};
			struct sockaddr_in
			{
				sa_family_t 	sin_family; //地址族
				uint16_t 		sin_port;//16位TCP/UDP端口号
				struct in_addr	sin_addr;//32位IP地址
				char 			sin_zero[8];//不使用
			};
			struct in_addr
			{
				In_addr_t		s_addr; //32位IPV4地址
			};

eg:
struct sockaddr_in addr;
service_addr.sin_family = AF_INET;//标识IPV4
service_addr.sin_port = htons(PORT);//之前宏定义的内容PORT
service_addr.sin_addr.s_addr = inet_addr(“172.17.0.49”);
//serv.sin_addr.s_addr=htonl(INADDR_ANY) 不知道可以不 按理说自动获取ip
AF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型,AF_INET6 则是 IPv6 的;而 AF_UNIX 则是 Unix 系统本地通信inet_aton是一个计算机函数,功能是将一个字符串IP地址转换为一个32位的网络序列IP地址

需要将客户端连接的ip改为服务端的ip地址
然后执行make命令 得到service 和client 文件
./service 启动服务端
./client 启动客户端
按照创建顺序可以输入想要发送的主机号 及内容

service:

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

#define PORT 8000
#define BACKLOG 5 
#define ERROR -1
#define SUCCESS 0
#define SIZE 1024

typedef struct Client_info
{
    int host;
    int dst;
    int client_socket;
    char buf[SIZE];
}_Client_info;

struct Client_info client_info[BACKLOG];
int client_num = 0;
pthread_t client_tid[BACKLOG];
int client_tidnum = 0;

void* handle_client(void* argv);

int checkTidIsKill(pthread_t tid)
{
    int res = 1;
    int res_kill = pthread_kill(tid,0);
    if(res_kill == 0)
    {
        res = 0;
    }
    return res;
}


int creat_socket()
{
    struct sockaddr_in service_addr;
    int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (ERROR == listen_socket)
    {
        printf("socket failed\n");
        return ERROR;
    }
    memset(&service_addr, 0, sizeof(service_addr)); //初始化service_addr
    service_addr.sin_family = AF_INET;
    service_addr.sin_port = htons(PORT);
    service_addr.sin_addr.s_addr = htonl(INADDR_ANY);//存疑 不知道可以自动获取不
    //service_addr.sin_addr.s_addr = inet_addr("172.17.0.3");
    int ret = bind(listen_socket, (struct sockaddr *)&service_addr, sizeof(service_addr));
    if ( ERROR == ret )
    {
        printf("bind failed\n");
        return ERROR;
    }
    ret = listen(listen_socket, BACKLOG);
    if (ERROR == ret)
    {
        printf("listen failed\n");
        return ERROR;
    }
    return listen_socket;
}

void wait_socket(int listen_socket)
{   
    struct sockaddr_in client_addr;
    struct Client_info client_in;
    int addrlen = sizeof(client_addr);
    memset(&client_addr, 0, sizeof(client_addr)); //初始化client_addr
    int i = 0;
    for(i=1; i <=5; i++)
    {
        client_in.host = i;
        int client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &addrlen);//如果是sizeof会出错吗??他是地址所以可能出错

        if (ERROR == client_socket)
        {
            printf("accept failed\n");
            exit(ERROR);
        }
        printf("成功收到一个客户端:%s\n", inet_ntoa(client_addr.sin_addr));//获得服务端的ip
        client_in.client_socket = client_socket;
        client_info[i] = client_in; 
        int ret = pthread_create(&client_tid[i], NULL, handle_client, &client_in);//循环进行获取多个客户端
        if (ERROR == ret)
        {
            printf("pthread_create failed\n");
            exit(ERROR);
        }
        sleep(0.2);
    }
}

void* handle_client(void* argv)
{
    struct Client_info client_in = *(struct Client_info* )argv;

    char buf[SIZE] = "";
    int ret = -1;
    int dst;
    char tmp[SIZE] = "";


    while(1)
    {   
        //scanf("%s", buf);
        ret = read(client_in.client_socket, buf, SIZE-1);
        if ( ERROR == ret)
        {
            printf("read failed\n");
            break;
        }
        if ( SUCCESS == ret)
        {
            printf("客户端断开连接\n");
            break;
        }
        buf[ret] = '\0'; //将字符串数组 变为 字符串
        sscanf(buf, "%d %s", &dst, tmp);
        client_in.dst = dst;



        ret = write(client_info[client_in.dst].client_socket, buf, strlen(buf));
        if ( ERROR == ret)
        {
            printf("write failed\n");
            exit(1);
        }
        sleep(0.5);
    }

}

int main()
{
    memset(&client_info ,0, sizeof(struct Client_info)*ba);
    int listen_socket = creat_socket();
    if (ERROR == listen_socket)
    {
        close(listen_socket);
        exit(ERROR);
    }
    wait_socket(listen_socket);
    close(listen_socket);
    return SUCCESS;
}

client:

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

#define PORT 8000
#define ERROR -1
#define SUCCESS 0
#define SIZE 1024

void* handle_read(void* argv)
{
    int client_socket = *(int*)argv;
    char buf[SIZE] = "";
    int ret = 0;
    while(1)
    {
        ret = read(client_socket, buf, SIZE - 1);
        if ( ret == ERROR )
        {
            printf("read failed\n");
            break;
        }
        if ( SUCCESS == ret)
        {
            printf("被断开连接\n");
            break;
        }
        buf[ret] = '\0';
        printf("来自服务端:%s\n", buf);
    }
    sleep(0.2);
}

int main()
{
    struct sockaddr_in addr;
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (ERROR == client_socket)
    {
        printf("socket failed\n");
        return ERROR;
    }
    memset(&addr, 0, sizeof(addr)); //初始化addr
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = inet_addr("172.17.0.3");// 你连接需要服务端的ip
    int addrlen = sizeof(addr);
    int ret = connect(client_socket, (struct sockaddr *)&addr, addrlen);//实现连接服务端
    if ( ERROR == ret )
    {
        printf("connect failed\n");
        exit(ERROR);
    }
    printf("成功连接到服务器,请输入内容:\n");
    pthread_t tid;
    ret = pthread_create(&tid, NULL, handle_read, &client_socket);
    if (ERROR == ret)
    {
        printf("pthread_create failed\n");
        exit(ERROR);
    }
    char buf[SIZE] = ""; //初始化字符串
    
    while(1)
    {
        //int c;
        //int j;
        printf("你想连接的客户端号(1-5数字)以及想发送的内容:\n");
        //scanf("%s", buf);
        fgets(buf, SIZE-1, stdin);
        if ( strcmp("end\n", buf) == SUCCESS )
        {
            printf("聊天结束\n");
            break;
        }
        write(client_socket, buf, SIZE-1);

        sleep(1);
    }

    pthread_detach(tid);
    close(client_socket);
    return SUCCESS;
}

makefile:
.PHONY: all clean
CC = gcc
CCFLAGS = -o
LCFLAGS = -lpthread
RM = rm
EXE = service client
all: $(EXE)
service:service.c
$(CC) $(CCFLAGS) $@ $^ $(LCFLAGS)
client:client.c
$(CC) $(CCFLAGS) $@ $^ $(LCFLAGS)
clean:
$(RM) $(EXE)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xiximimio/article/details/100019882

智能推荐

从本地或者网络读取图片,并转换为Bitmap图片_java 读取网页图像到bitmap-程序员宅基地

文章浏览阅读6.6k次。在做android项目时,我们经常需要从本地或者网络读取图片,并转换为Bitmap图片,以便使用,下面是读取本地图片并转换的方法:Java代码 /** * 得到本地或者网络上的bitmap url - 网络或者本地图片的绝对路径,比如: * * A.网络路径: url="http://blog.foreverlov_java 读取网页图像到bitmap

计算机组成原理|多功能ALU设计实验_设计一个具有8种运算功能的32位alu实验总结-程序员宅基地

文章浏览阅读9.8k次,点赞7次,收藏118次。多功能ALU设计实验一、实验目的与要求实验目的:(1)学习多功能ALU的工作原理,掌握运算器的设计方法(2)掌握运用Verilog HDL 进行行为描述与建模的技巧和方法实验要求:本实验要求设计一个具有8种运算功能的32位ALU,并能够产生运算结果的标志:结果为零标志ZF(Zero Flag)、溢出标志OF(Overflow Flag)。ALU通过3根控制线ALU_OP[2:0]..._设计一个具有8种运算功能的32位alu实验总结

iOS开发进阶之列表加载图片-程序员宅基地

文章浏览阅读484次,点赞10次,收藏5次。列表加载图片通常使用UITableView或UICollectionView,由于列表中内容数量不确定并且对于图片质量要求也不确定,所以对于图片加载的优化是很有必要的。

29、基于51单片机智能消防灭火小车 寻光自动红外壁障车设计_灭火小车设计方案-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏16次。智能作为现代的新发明,是以后的发展方向,他可以按照预先设定的模式在一个环境里自动的运作,不需要人为的管理,可应用于科学勘探等等的用途。智能小车就是其中的一个体现,本次设计的多功能智能灭火避障小车,以STC89C52单片机作为微控制器,设计出一种可以寻找火源(火源以蜡烛模拟)和自动避开障碍物的小车。通过光敏晶体管传感器检测火源信号当检测到火源,小车自动调整姿态,对准火源。灭火电机启动将蜡烛吹灭实现模拟灭火。通过红外光电开关感应控制小车避障行驶。工作状态实时显示在1602液晶上。_灭火小车设计方案

Ubuntu虚拟机总是死机,然后重启就进不去打不开了怎么办_ubuntu卡死之后重启,打不开了-程序员宅基地

文章浏览阅读4.6k次。从网上搜到的方法都解决不了我的问题,分享一点我自己的实在的解决经验:养成保存快照的习惯,比什么方法都靠谱。 即打开VMWare->虚拟机->快照->拍摄快照,简单填写一下你现在做到的程度,然后点击拍摄快照,存一下,养成习惯,每做出些什么东西了,就存一个快照,哪天莫名其妙又死机了,开机又黑屏进不去了,直接用快照恢复到最近的进度继续做就好。..._ubuntu卡死之后重启,打不开了

随便推点

Snipaste的使用_snipaste使用-程序员宅基地

文章浏览阅读7.3k次,点赞5次,收藏11次。Snipaste的使用_snipaste使用

使用python下载加密的流媒体m3u8视频文件,获取电影资源-程序员宅基地

文章浏览阅读273次,点赞3次,收藏8次。Python崛起并且风靡,因为优点多、应用领域广、被大牛们认可。学习 Python 门槛很低,但它的晋级路线很多,通过它你能进入机器学习、数据挖掘、大数据,CS等更加高级的领域。Python可以做网络应用,可以做科学计算,数据分析,可以做网络爬虫,可以做机器学习、自然语言处理、可以写游戏、可以做桌面应用…Python可以做的很多,你需要学好基础,再选择明确的方向。这里给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

在Qt中使用CreateProcess打开命令行并执行命令_qt.createqprocess()-程序员宅基地

文章浏览阅读351次。在Qt应用程序中,执行命令行命令并获取输出结果可以使用QProcess类。现在,我们可以在Qt应用程序中调用这个函数来执行命令行命令并获取输出结果。现在,我们可以在Qt应用程序中调用这个函数来执行命令行命令并获取输出结果。在上面的示例中,我们将命令设置为"dir",这将列出当前目录的内容。在上面的示例中,我们将命令设置为"dir",这将列出当前目录的内容。这样,当我们点击按钮时,程序将执行命令并将输出结果显示在文本编辑器中。这样,当我们点击按钮时,程序将执行命令并将输出结果显示在文本编辑器中。_qt.createqprocess()

java logutil_Java日志组件1---Jdk自带Logger(java.util.logging.Logger)-程序员宅基地

文章浏览阅读216次。最近在看日志的一些东西,发现利用JDK自带的log也可以简单的实现日志的输出,将日志写入文件的过程记录如下:1、新建LogUtil.Java(里面写了几个静态方法,为log设置等级、添加log控制台handler、添加log文件输出handler)packagecn.darkranger.log.logger;importjava.io.IOException;importjava.text.S..._logutil.java

此时不应有 \scala\bin\..\lib\jline-2.14.5.jar_c:\users\dell>scala 此时不应有 \scala\bin\..\lib\jline--程序员宅基地

文章浏览阅读221次。scala安装时,此时不应有 \scala\bin…\lib\jline-2.14.5.jar那是因为安装Scala时,默认安装到Program Files (x86)或者Program Files下,但是这俩个文件夹命名存在空格,这是从新安装,选择没有空格存在的文件夹下面即可。谢谢..._c:\users\dell>scala 此时不应有 \scala\bin\..\lib\jline-2.14.5.jar

本地搭建docker仓库的详细步骤_本地安装docker-程序员宅基地

文章浏览阅读1.7k次。在本地创建一个用于存储Docker镜像的目录,比如 /data/docker-registry。如果需要加速拉取公共镜像,可以配置阿里云或DaoCloud等国内提供的Docker镜像加速器。如果需要在其他机器上访问该私有仓库,则需要配置证书。使用docker tag将本地构建好的镜像打标签,并推送到私有仓库中。在官网下载Docker安装包进行安装,具体操作方式可以参考官方文档。至此,本地Docker仓库搭建完成。配置客户端访问证书(可选)拉取Registry镜像。启动Registry容器。_本地安装docker