文章目录
- 前言
- 基本原理
- Code
- server
- client
- 核心函数
- socket
- bind
- listen
- accept
- recv
- send
- connect
- close
- 多线程改进
- END
前言
本文将以纯C语言描述,编译器gcc。
C/C++没有标准的网络库,因为都需要用到各个平台的接口才行。
本文讲解Linux下最基础的socket编程,实现一个简单的回声服务器。
基本原理
具体流程如下:
此图描述的非常清晰,就是一步一步往下操作即可。
Code
废话不多说,直接上code
cv代码后直接可以编译运行
注意:
- 请先启动server再启动client
- 如出现异常,请确认端口是否可以用
- 如果是云端操作,请注意ip
server
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main() {
printf("***** server main start ******\n");
// 1. 创建监听的套接字
// ipv4 流式传输
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
printf("socket = -1");
return -1;
}
// 配置基本信息
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
// 主机字节序转为网络字节序
// 设定一个没有冲入的端口
saddr.sin_port = htons(12345);
// any就是0->0.0.0.0 此时不区分大小端
// 自动定读网卡的实际ip地址
saddr.sin_addr.s_addr = INADDR_ANY;
// 2. 绑定socket
int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));
if (ret == -1) {
printf("bind = -1");
return -1;
}
// 3. 设置监听
// 最大128
ret = listen(fd, 128);
if (ret == -1) {
printf("listen = -1");
return -1;
}
printf("***** ready to accept ******\n");
struct sockaddr_in caddr;
int addrlen = sizeof(struct sockaddr_in);
// 4. 阻塞并等待客户端的连接
// 成功则获得客户端的fd
int cfd = accept(fd, (struct sockaddr*)&caddr, &addrlen);
if (cfd == -1) {
printf("accept = -1");
return -1;
}
// 连接成功,打印客户端ip和端口
char ip[32] = {};
printf("client ip = %s, port = %d\n",
inet_ntop(AF_INET, &caddr.sin_addr.s_addr, ip, sizeof(ip)),
ntohs(caddr.sin_port));
// 5. 通信
while (1) {
char buff[1024] = {};
int len = recv(cfd, buff, sizeof(buff), 0);
if (len > 0) {
printf("client say : %s\n", buff);
// 原版不动的回传
send(cfd, buff, len, 0);
} else if (len == 0) {
printf("client close ... \n");
break;
} else {
printf("recv = -1\n");
break;
}
}
// 6. 关闭文件描述符
close(fd);
close(cfd);
printf("***** server main end ******\n");
return 0;
}
client
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main() {
printf("***** client main start ******\n");
// 1. 创建通信的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
printf("socket = -1");
return -1;
}
// 连接服务器
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(12345);
inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);
// 2. 绑定socket
int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
if (ret == -1) {
printf("connect = -1");
return -1;
}
int number = 0;
// 3. 通信
while (1) {
// 发送
char buff[1024] = {};
sprintf(buff, "Hello i am client [%d]...\n", number++);
send(fd, buff, strlen(buff) + 1, 0);
// 接受
memset(buff, 0, sizeof(buff));
int len = recv(fd, buff, sizeof(buff), 0);
if (len > 0) {
printf("server say : %s\n", buff);
} else if (len == 0) {
printf("server close ... \n");
break;
} else {
printf("read = -1\n");
break;
}
// 延时2秒
sleep(2);
}
// 4. 关闭文件描述符
close(fd);
printf("***** client main end ******\n");
return 0;
}
核心函数
头文件
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
socket
用于创建socket对象
前两个参数决定了使用什么通信协议。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// ipv4 的流式 就是tcp
socket(PF_INET, SOCK_STREAM, 0);
// ipv4 的报文 就是udp
socket(PF_INET, SOCK_DGRAM, 0);
bind
服务端客户端通用
#include <sys/types.h>
#include <sys/socket.h>
// const 是入参
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen
服务端开启监听
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
accept
服务端等待客户端的接入
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
// 这里是回参
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
recv
接收数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
send
发送数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
connect
客户端去连接服务端
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
// const 是入参
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
close
关闭fd的通用函数
#include <unistd.h>
int close(int fd);
多线程改进
注意编译的时候要加入-lpthread
线程函数的接口是void* () (void*)
经过改进后可以并发接入多个客户端的连接。
#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/**
* 将 struct sockaddr_in 和 fd 绑定到一起
*/
struct SockInfo {
struct sockaddr_in addr;
int fd;
};
struct SockInfo infos[512];
/**
* pthread 固定函数类型
* void* () (void*)
*/
void* working(void* arg) {
// 指针的强转
struct SockInfo* pinfo = (struct SockInfo*)arg;
// 连接成功,打印客户端ip和端口
char ip[32] = {};
printf("client ip = %s, port = %d\n",
inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),
ntohs(pinfo->addr.sin_port));
// 5. 通信
while (1) {
char buff[1024] = {};
int len = recv(pinfo->fd, buff, sizeof(buff), 0);
if (len > 0) {
printf("client say : %s\n", buff);
send(pinfo->fd, buff, len, 0);
} else if (len == 0) {
printf("client close ... \n");
break;
} else {
printf("recv = -1\n");
break;
}
}
// 6. 关闭文件描述符
close(pinfo->fd);
pinfo->fd = -1;
printf("thread end success\n");
return NULL;
}
int main() {
printf("***** server main start ******\n");
// socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
printf("socket = -1");
return -1;
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(12345);
saddr.sin_addr.s_addr = INADDR_ANY;
// bind
int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));
if (ret == -1) {
printf("bind = -1");
return -1;
}
// listen
ret = listen(fd, 128);
if (ret == -1) {
printf("listen = -1");
return -1;
}
// 初始化所有封装的 struct
printf("********ready to accept**********\n");
int max = sizeof(infos) / sizeof(infos[0]);
for (int i = 0; i < max; i += 1) {
// memset 0
bzero(&infos[i], sizeof(infos[i]));
infos[i].fd = -1;
}
// accept
// thread
int addrlen = sizeof(struct sockaddr_in);
while (1) {
// 找出可用空间
struct SockInfo* pinfo = NULL;
for (int i = 0; i < max; i += 1) {
if (infos[i].fd == -1) {
pinfo = &infos[i];
break;
}
}
int cfd = accept(fd, (struct sockaddr*)&pinfo->addr, &addrlen);
pinfo->fd = cfd;
// 根据自己的需要处理失败的 accept
if (cfd == -1) {
printf("accept = -1");
break;
}
// 创建子线程,并与主线程分离
pthread_t tid;
pthread_create(&tid, NULL, working, pinfo);
pthread_detach(tid);
}
// 关闭fd
close(fd);
printf("***** server main end ******\n");
return 0;
}
END
参考:套接字 - Socket | 爱编程的大丙 (subingwen.cn)
Qt多线程网络通信-套接字通信 socket_哔哩哔哩_bilibili