声明

这是本菜鸡的笔记总结,可能会有一些自己的主观。如有错误还请指正。

所用的函数

  1. socket()
    功 能:创建套接字
    原 型: int socket(int domain, int type, int protocol);
    参 数:
    domain:协议族,通常为 AF_INET,表示 TCP/IP 协议
    type: socket 类型,如: SOCK_STREAM(指 TCP)和
    SOCK_DGRAM(指 UDP)等等
    protocol:套接口所用的协议,一般为 0
    返回值:
    成功: socket 文件描述符
    失败: -1,并设置 errno
  2. bind()
    功 能:将套接字和指定的端口相连
    原 型: int bind(int sock_fd, struct sockaddr_in *my_addr, int addrlen);
    参 数:sock_fd: socket 文件描述符
    my_addr:设置服务器信息的 sockaddr_in 结构体指针
    addrlen: sockaddr_in 结构体的长度
    返回值:
    成功: 0
    失败: -1,并设置 errno
    说 明:
    struct sockaddr_in{
    short int sin_family; //网络通信网络层协议
    AF_INET
    unsigned short int sin_port; //端口
    struct in_addr sin_addr; //IP 地址
    unsigned char sin_zero[8]; //让 sockaddr 与
    sockaddr_in 两个数据结构保持大小相同而保留的空字节
    };
  3. connect()
    功 能:连接服务器请求(客户端使用)
    原 型: int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
    参 数:
    sock_fd: socket 文件描述符
    serv_addr:包含远端主机 IP 地址和端口号的指针
    addrlen: sockaddr_in 结构体的长度
    返回值:
    成功: 0
    失败: -1,并设置 errno
  4. listen()
    功 能:创建一个套接口并监听申请的连接
    原 型: int listen(int sock_fd, int backlog);
    参 数:
    sock_fd: socket 文件描述符
    backlog:请求队列中允许的最大请求数
    返回值:
    成功: 0
    失败: -1,并设置 errno
  5. accecpt()
    功 能:接受客户端的服务请求
    原 型: int accept(int sock_fd, struct sockadd_in* addr, int addrlen);
    参 数:
    sock_fd:被监听的 socket 文件描述符
    addr:包含客户端 IP 地址和端口号的指针
    addrlen: sockaddr_in 结构体的长度返回值:
    成功:客户端套接字描述符
    失败: -1,并设置 errno
  6. write()
    功 能:写入数据到 fd 中
    原 型: ssize_t write(int fd,const void *buf,size_t nbytes);
    参 数:
    fd: socket 文件描述符
    buf:字符串数据地址
    nbytes:字符串数据大小
    返回值:
    实际写入的字节数,小于 0 为写入错误
    6、 read()
    功 能:从 fd 中读取数据
    原 型: ssize_t read(int fd,void *buf,size_t nbyte)
    参 数:
    fd: socket 文件描述符
    buf:字符串数据地址
    nbyte:字符串数据大小
    返回值:

0: 实际读取的大小
=0:读到末尾了
<0:读取错误
8、 close()
功 能:关闭套接字
原 型: int close(sock_fd);
参 数:
sock_fd:要关闭的 socket 文件描述符
返回值:
成功: 0
失败: -1

进程线程的创建和释放

2.2:线程的创建

pthread_create (thread, attr, start_routine, arg)
thread:进程号(自己定义的)
attr:NULL
start_routine:开启的函数名称(也就是其首地址)
arg :NULL

2.3:资源的释放(在这里我使用的是unjoinable模式)pthread_detach(pthread_self());这个函数可以使我们的线程在退出时立即释放资源。

pthread有两种状态joinable状态和unjoinable状态,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。
2.unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己(我们这就是这样创建), 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。或者将线程置为 joinable,然后适时调用pthread_join.
3.其实简单的说就是在线程函数开始加上 pthread_detach(pthread_self()),线程状态改变,在函数尾部直接调用pthread_exit()线程就会自动退出。

pthread_join()即是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源。

#头文件解读
3.1:网络编程用的头文件
#include <sys/socket.h>
3.2:线程用的头文件
#include <pthread.h>

特定结构体。

4.1:sockaddr_in为协议族,地址,端口等必要信息构成的结构体,对信息的使用方便很多。

代码

服务器端的
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <unistd.h>int SERVER_PORT = 8091;typedef unsigned char u8;
typedef char cmd_key_t;
typedef int mid_t;
#define MESSAGE_LENGTH_MAX  512typedef struct
{cmd_key_t cmd_key; //mid_t own;mid_t peer;u8 message[MESSAGE_LENGTH_MAX];
} client_message_t;int Gfd[1024];int _id2peer_trans_func_(int prid)
{//int socket_handler = prid --> cms->peer;int socket_handler = Gfd[prid];return(socket_handler);
}void *_t_recv(void *arg)
{int fd = *((int *)arg);pthread_detach(pthread_self());while(1){unsigned char buffer[1024];int ret = recv(fd,buffer,sizeof(client_message_t),MSG_WAITALL);if(ret > 0){client_message_t *cms = (client_message_t *)buffer;if(cms->cmd_key  == 'L'){printf("[S]new login[%d],his handler is %d\r\n",cms->own,fd);Gfd[cms->own] = fd;strcpy(cms->message,"[S]welcome\r\n");send(_id2peer_trans_func_(cms->own),cms,sizeof(client_message_t),MSG_WAITALL);}else{printf("   [S] #ID%d/FD%d# --> @ID%d / FD%d@\r\n",cms->own,_id2peer_trans_func_(cms->own),cms->peer,_id2peer_trans_func_(cms->peer));send(_id2peer_trans_func_(cms->peer),cms,sizeof(client_message_t),MSG_WAITALL);}}else{perror("[S] recv:");break;}}printf("close the peer.\r\n");close(fd);pthread_exit(NULL);
}int main()
{//调用 socket 函数返回的文件描述符int serverSocket;//声明两个套接字 sockaddr_in 结构体变量,分别表示客户端和服务器struct sockaddr_in server_addr;int addr_len = sizeof(server_addr);int client;if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0){perror("socket");}bzero(&server_addr, sizeof(server_addr));//初始化服务器端的套接字,并用 htons 和 htonl 将端口和地址转成网络字节序server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("connect");}if (listen(serverSocket, 5) < 0){perror("listen");}printf("[S]Listening on port: %d\n", SERVER_PORT);while(1){struct sockaddr_in clientAddr;client = accept(serverSocket, (struct sockaddr *)&clientAddr, &addr_len);if (client < 0){perror("accept");continue;}pthread_t trecvid, tsendid;pthread_create(&trecvid, NULL, _t_recv, &client);}
}

这个服务端的函数的重点我在这个里解释一下,

1: typedef struct client_message_t,顾名思义这个是客户端发来的消息结构体 内容是消息的类型(最后要我们实现的就是可以发消息和文件、视频)、消息的发送方和接受方,消息内容
2: id2peer_trans_func 这个是数组下标和套接字映射函数。我们登录后要输入一个“机号”——数组下标,服务器就会把生成的客户端套接字存进对应下标的数组内。这可以”弱化“套接字的影响,即使我们退出重进只要你输入的是原来的“机号”你的小朋友还是可以根据“机号”和你交往。
3: void *_t_recv(void *arg)这就是我们开的线程,里面根据客户端信息体中的信息类型将功能分为2部分当cmd_key == 'L’时就是登录其它就是根据目标“机号转发信息的过程。

因为我们在进行多个客户端的通信中要知道对方的套接字,但是我们正常情况下只知道对方的编号(套接字是随机的,所以如果你退出后你再重新上线后套接字就又变了,非常难搞。)

客户端代码
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <unistd.h>int SERVER_PORT = 8091;typedef unsigned char u8;
typedef char cmd_key_t;
typedef int mid_t;
#define MESSAGE_LENGTH_MAX  512typedef struct
{cmd_key_t cmd_key;mid_t own;mid_t peer;u8 message[MESSAGE_LENGTH_MAX];
} client_message_t;void *_t_recv(void *arg)
{int fd = *((int *)arg);while(1){unsigned char buffer[1024];int ret = recv(fd,buffer,sizeof(client_message_t),MSG_WAITALL);if(ret > 0){client_message_t *cms = (client_message_t *)buffer;printf("   [C]message from[%d]:%s\r\n",cms->own,cms->message);}}
}int main()
{//创建 socket 对象int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (0 > sockfd){perror("socket");return -1;} //准备通信地址struct sockaddr_in addr = {};addr.sin_family = AF_INET;addr.sin_port = htons(8091); //端口号addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //本地 ip//连接if (0 > connect(sockfd, (struct sockaddr *)&addr, sizeof(addr))){perror("connect");return -1;}pthread_t trecvid;pthread_create(&trecvid, NULL, _t_recv, &sockfd);client_message_t cms;memset(&cms,0,sizeof(cms));printf("[C]input your id(0~1024),then Enter:");fflush(stdout);scanf("%d",&cms.own);cms.cmd_key = 'L';int r = send(sockfd,&cms,sizeof(cms),MSG_WAITALL);printf("[C]we are login to svr<%d Bytes>.\r\n",r);while(1){cms.cmd_key = 'M';printf("[C]input peer id(0~1024),then Enter:");fflush(stdout);scanf("%d",&cms.peer);printf("[C]input message send to [%d],then Enter:",cms.peer);fflush(stdout);scanf("%s",cms.message);send(sockfd,&cms,sizeof(cms),MSG_WAITALL);}}

认真读完服务端的化客户端就是小菜半碟。注意的也就是fflush函数,因为我们输入数据后要回车,但是这个回车在不加fflush的情况下会被后面的scanf获取。这不是我们想要的结果。

总结

这个代码的逻辑还是比较简单的,但要自己写出来不是那么简单的。

【日常积累】实验室作业Socket实现多个客户端相互通信。相关推荐

  1. Socket实现服务器端与客户端之间通信(输入文字聊天)

    用Socket实现服务器端与客户端之间通信 需求: 键盘输入文字使服务器端与客户端可以实现文字通信. 使用的是TCP协议. TCP协议 客户端 创建Socket连接服务端(指定ip地址,端口号)通过i ...

  2. C# Socket服务端与客户端通信(包含大文件的断点传输)

    步骤: 一.服务端的建立 1.服务端的项目建立以及页面布局 2.各功能按键的事件代码 1)传输类型说明以及全局变量 2)Socket通信服务端具体步骤:   (1)建立一个Socket   (2)接收 ...

  3. eclipse打包项目为aar_新生日常牢骚之作业打包

    新生日常牢骚之作业打包 上大学作业不会交,不会吧不会吧 会的,不知道啊呜呜呜 如何把作业打包带走 当在完成我们Java老师留的作业后,有没有困扰要怎样把我们的作业打包带走发给的老师勒,嘿嘿今天我就来写 ...

  4. Java程序员日常积累-向大神学习(三)

    前言 <Java程序员日常积累-向大神学习>系列是本人根据日常工作中碰到的问题写的一个问题记录,比较琐碎零散. 主要用于记录和忘记时可以有个地方查询.现分享给大家,这是这个系列的第三篇. ...

  5. 大数据实验室作业总结

    大数据实验室作业总结 import re import requests import json url = 'http://music.taihe.com/search' singer = { &q ...

  6. 基于MFC的socket编程(异步非阻塞通信)

    对于许多初学者来说,网络通信程序的开发,普遍的一个现象就是觉得难以入手.许多概念,诸如:同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)等,初学者往往迷惑不清,只知其 ...

  7. 【★更新★】高性能 Windows Socket 服务端与客户端组件(HP-Socket v2.0.1 源代码及测试用例下载)...

    HP-Socket 以前为某大型通信项目开发了一套通用 Windows Socket TCP 底层通信组件,组件代号为 HP-Socket.现在把 HP-Socket 的所有代码向大众公开,希望能对大 ...

  8. 使用python中的socket实现服务器和客户端,并完成图片的传输

    使用python中的socket实现服务器和客户端,并完成图片的传输. 2018年03月09日 16:05:23 阅读数:301 socket服务器代码: [python] view plaincop ...

  9. Java中利用socket实现简单的服务端与客户端的通信(中级)——实现任意双向通信

    本文计划采用socket实现客户端和服务端的任意双向通信,即客户端可以随时给服务端发消息,服务端也可以随时给客户端发消息,最终结果就是一个类似与QQ的聊天软件的功能. 以下代码可以直接拷贝到Eclip ...

最新文章

  1. 详解math.isclose()用法
  2. Java不同场景加载不同类_[改善Java代码]不同的场景使用不同的泛型通配符
  3. Session会话技术
  4. 二进制安装kubernetes v1.11.2 (第八章 kube-apiserver 部署)
  5. Java8 Stream详解~筛选:filter
  6. ramfs, rootfs and initramfs
  7. 周鸿祎重申360不依靠弹窗广告盈利 增值服务是主要模式
  8. 计算机基础(三):srpintf()函数小结
  9. swing JTable学习(七)—TableModelListener
  10. MSCRM4.0商机移除价目表引起的问题
  11. 查看python的模块和函数帮助文档方法
  12. OpenGL ——安装和环境配置
  13. IDEA中使用JUnit4(单元测试框架)超详细!
  14. 诗词温习集:跟梁瀚文一起重温诗词(唐诗)之《春望》
  15. 上课笔记-指针(从百草园到三味书屋)
  16. c语言患者住院管理系统,住院系统-中小医院医疗套装软件管理系统_九明珠信息科技...
  17. linux中关闭防火墙
  18. 用HTML5+CSS实现3d动画立方体
  19. crm客户关系管理系统总结
  20. Modbus-异常响应

热门文章

  1. mysql 查询上一周每一天的数据(含跨年问题)
  2. python生成word文档_python实现的生成word文档功能示例
  3. 为什么独热编码会引起维度诅咒,以及避免他的几个办法
  4. cancase vector_低價替代Vector CANoe CAN總線適配解決方案支持所有USBCAN(周立功CAN、PCAN、Kvaser、ValueCAN、NI CAN)...
  5. MySQL数据库安装包官网下载地址
  6. 蓝桥杯51单片机学习——proteus8关于51工程文件的创建
  7. java 存储空间 简单分析
  8. 玩转X-CTR100 l STM32F4 l DSP指令集性能测试
  9. 使用searx搭建自己的搜索引擎
  10. QT5百度地图开发学习——地图显示