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)

  • 7
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,这是一个简单的示例代码,包括服务端客户端的代码: 服务端代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") //链接ws2_32.lib库文件 #define BUF_SIZE 1024 #define PORT 8888 int main() { WSADATA wsaData; SOCKET servSock; SOCKET clntSock; char buffer[BUF_SIZE]; int strLen; SOCKADDR_IN servAddr; SOCKADDR_IN clntAddr; int clntAddrSize; // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed!\n"); return 1; } // 创建Socket servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (servSock == INVALID_SOCKET) { printf("socket failed!\n"); WSACleanup(); return 1; } // 绑定Socket memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = htonl(INADDR_ANY); servAddr.sin_port = htons(PORT); if (bind(servSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) { printf("bind failed!\n"); closesocket(servSock); WSACleanup(); return 1; } // 监听Socket if (listen(servSock, 5) == SOCKET_ERROR) { printf("listen failed!\n"); closesocket(servSock); WSACleanup(); return 1; } // 接受客户端请求 clntAddrSize = sizeof(clntAddr); clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &clntAddrSize); if (clntSock == INVALID_SOCKET) { printf("accept failed!\n"); closesocket(servSock); WSACleanup(); return 1; } // 与客户端进行通信 while (1) { memset(buffer, 0, BUF_SIZE); strLen = recv(clntSock, buffer, BUF_SIZE, 0); if (strLen == SOCKET_ERROR) { printf("recv failed!\n"); break; } send(clntSock, buffer, strLen, 0); } // 关闭Socket closesocket(clntSock); closesocket(servSock); WSACleanup(); return 0; } ``` 客户端代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") //链接ws2_32.lib库文件 #define BUF_SIZE 1024 #define PORT 8888 int main() { WSADATA wsaData; SOCKET sock; char buffer[BUF_SIZE]; int strLen; SOCKADDR_IN servAddr; // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed!\n"); return 1; } // 创建Socket sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { printf("socket failed!\n"); WSACleanup(); return 1; } // 连接服务器 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(PORT); if (connect(sock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) { printf("connect failed!\n"); closesocket(sock); WSACleanup(); return 1; } // 与服务器进行通信 while (1) { memset(buffer, 0, BUF_SIZE); printf("Please input a string: "); gets_s(buffer, BUF_SIZE); if (strcmp(buffer, "exit") == 0) { break; } send(sock, buffer, strlen(buffer), 0); strLen = recv(sock, buffer, BUF_SIZE, 0); if (strLen == SOCKET_ERROR) { printf("recv failed!\n"); break; } printf("Message from server: %s\n", buffer); } // 关闭Socket closesocket(sock); WSACleanup(); return 0; } ``` 注意:以上代码仅供参考,实际使用中还需要进行错误处理和异常情况处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值