C++网络学习笔记
网络IO
Socket套接字
一、什么是socket
Socket(套接字)是计算机网络编程中的一种抽象概念,它提供了在网络上进行通信的接口。通过使用 Socket,可以在不同计算机之间建立连接,并进行数据的传输和交换。
Socket 可以用于实现各种网络应用,例如:
1. 客户端-服务器通信:通过 Socket,客户端可以与服务器建立连接并发送请求,服务器接收请求并返回响应。
2. 网络聊天和即时通讯:通过 Socket,可以在多个用户之间实现实时的文字、音频或视频通信。
3. 文件传输:可以使用 Socket 在不同计算机之间传输文件,如上传和下载文件。
4. 远程控制:可通过 Socket 在远程计算机上执行指令或操作。
5. P2P(点对点)通信:Socket 提供了直接的点对点通信接口,允许两个或多个计算机直接进行数据交换。
具体使用 Socket 进行网络编程时,需要考虑协议、端口、IP 地址、数据格式等因素,以确保通信的正确性和有效性。不同编程语言和平台都有相应的 Socket API 和库,可以根据自己的需求选择合适的工具和技术。
在 C 语言中,socket 是一种特殊的文件描述符,它允许程序通过网络与其他程序通信。socket 编程通常包含以下步骤:
- 创建 socket。
- 绑定 IP 地址和端口(服务器)。
- 监听和接受连接(服务器)。
- 连接到服务器(客户端)。
- 发送和接收数据。
- 关闭连接。
socket(int domain, int type, int protocol):创建 socket,返回 socket 文件描述符。
domain:地址族,如 AF_INET(IPv4)或 AF_INET6(IPv6)。
type:通信类型,如 SOCK_STREAM(面向连接)或 SOCK_DGRAM(面向数据报)。
protocol:通常为 0(自动选择协议)。
bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen):将 socket 绑定到指定的 IP 地址和端口。
listen(int sockfd, int backlog):将 socket 设置为监听模式,backlog 指定等待连接的队列长度。
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen):接受客户端连接,返回新的 socket 文件描述符。
connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen):连接到服务器。
send(int sockfd, const void *buf, size_t len, int flags) 和 recv(int sockfd, void *buf, size_t len, int flags):发送和接收数据。
close(int sockfd):关闭 socket 连接。
pthread.h实现多线程
1.线程创建的目标
pthread_create的主要任务就是在一个进程中创建一个新的线程,新的线程共享地址空间和部分资源。
2.使用clone() 系统调用
在Linux中,线程的创建的核心就是使用clone()。clone()与fork()相似,但是比后者更加的灵活,其可以控制新线程和父进程共享的资源和属性。
线程创建的流程:
线程创建的大体流程如下:
准备线程属性:
pthread_create 首先会根据传入的线程属性(如栈大小、调度策略)准备必要的资源。如果没有指定,使用默认值。
例如,默认情况下,新线程会继承进程的地址空间、文件描述符表、信号处理器等。
设置栈空间:
为新线程分配栈空间,通常通过映射新的内存区域来完成(使用 mmap() 或现有栈空间)。
栈空间是线程私有的,即每个线程都有自己的栈,以避免不同线程之间的数据混淆。
调用 clone():
pthread_create 通过 clone() 系统调用创建新线程。clone() 创建一个新的轻量级进程(LWP),它与调用者共享资源(如地址空间),但有自己的栈和执行上下文。
通过 flags 参数指定哪些资源要共享,常见的标志包括:
CLONE_VM: 共享内存空间。
CLONE_FS: 共享文件系统信息。
CLONE_FILES: 共享文件描述符表。
CLONE_SIGHAND: 共享信号处理。
在新线程中执行函数:
clone() 成功后,新线程开始执行 fn 函数,并将传递的 arg 作为参数。
新线程执行函数 fn,直到它返回或显式调用 pthread_exit。
线程 ID 的管理:
pthread_create 返回时,会将新创建线程的 ID (pthread_t) 传给调用者。这通常是一个整数(或指针),在系统中唯一标识该线程。
内核会跟踪这个线程 ID 以及它的相关信息(如状态、栈指针等)。
代码实现
首先是简单的建立TCP通信:
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#int main(){
int sockfd = socket(AF_INET,SOCK_STREAM,0);
/*
AF_INET:使用IPv4地址族
SOCK_STREAM:创建TCP套接字
0:使用默认协议(对于SOCK_STREAM是TCP)
返回的sockfd是一个套接字描述符,用于后续的网络操作。
*/
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(struct sockaddr_in);//声明serveraddr用于存储地址信息
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(2048);
/*
设置地址族为IPv4
设置IP地址为INADDR_ANY ,表示服务器将监听所有可用的网络接口(即0.0.0.0)
设置服务器端口号为2048
*/
if(-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)){
perror("bind");
return -1;
}
//将套接字与指定的地址和端口绑定,若失败返回-1
listen(sockfd, 10);
getchar();
}
编译执行以上代码,我们就在2048端口上建立了一个TCP服务器,但是这个服务器目前只能供客户端连接,不能对客户端的交互做出任何反应。
我们可以添加一个连接后做出反馈的功能:
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#int main(){
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(struct sockaddr_in);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(2048);
if(-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)){
perror("bind");
return -1;
}
listen(sockfd, 10);
struct sockaddr_in clientaddr;//声明clientaddr用于存储客户端地址
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
//调用 accept 函数接受客户端连接请求。该函数会阻塞,直到有客户端连接为止。
printf("sockfd: %d,clientfd:%d\n", sockfd, clientfd);
getchar();
}
编译运行后,我们再按任意键不会直接退出也不会有任何反应,直到我们的客户端连接之后,终端会打印出如下的数据:
sockfd:3,clienrfd:4
现在,我们让服务器返回客户端发送的信息
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#int main(){
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(struct sockaddr_in);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(2048);
if(-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)){
perror("bind");
return -1;
}
listen(sockfd, 10);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf(“accept\n);
char buffer[128] = {0};
int count = recv(clientfd, buffer, sizeof(buffer), 0);
/*
调用 recv 函数从客户端接收数据。
- clientfd :客户端套接字描述符
- buffer :存储接收数据的缓冲区
- sizeof(buffer) :缓冲区大小
*/
send(clientfd, buffer, count, 0);
/*
调用 send 函数将接收到的数据发送回客户端(回显功能)。
参数与 recv 类似,但这里发送的是 count 个字节,即实际接收到的数据量。
*/
printf("sockfd: %d,clientfd:%d\n,count:%d", sockfd, clientfd,count);
getchar();
close(clientfd);
}
现在,服务器会回显客户端发送的数据

c此时只能进行单次回显,所以我们可以使用while循环来不断的接收数据:
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#int main(){
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(struct sockaddr_in);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(2048);
if(-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)){
perror("bind");
return -1;
}
listen(sockfd, 10);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf(“accept\n);
while(1){
char buffer[128] = {0};
int count = recv(clientfd, buffer, sizeof(buffer), 0);
//当recv返回0时,说明对端关闭了连接
if(count == 0){
break;
}
send(clientfd, buffer, count, 0);
printf("sockfd: %d,clientfd:%d,count = %d\n", sockfd, clientfd,count);
}
getchar();
close(clientfd);
}
现在服务器可以不断的向发送数据的客户端回显数据了,但是如果当客户端断开连接时,程序会卡住,此时端口占用会显示"CLOSE_WAIT"
tcp 0 0 192.168.146.129:2048 192.168.146.1:50701 CLOSE_WAIT - off (0.00/0/0)
此时如果我们用另一个客户端来连接服务器是可以连接的,但是服务器不会对客户端发送的数据做任何反应。此时我们需要引入线程来建立一个基本的网络IO:
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
void *client_thread(void *arg){
int clientfd = *(int *)arg;
while(1){
char buffer[128] = {0};
int count = recv(clientfd, buffer, sizeof(buffer), 0);
if(count == 0){
break;
}
send(clientfd, buffer, count, 0);
printf("clientfd:%d,count = %d,buffer: %s\n", clientfd,count,buffer);
}
close(clientfd);
}
//tcp
int main(){
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);//0.0.0
serveraddr.sin_port = htons(2048);
if(-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))){
perror("bind");
return -1;
}
listen(sockfd, 10);
printf("accept\n");
while(1){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
pthread_t thid;
pthread_create(&thid, NULL, client_thread, &clientfd);
}
getchar();
}
由此,我们便实现了最基本的网络IO。(未引入IO复用, 每个客户端分配一个线程)
更多推荐



所有评论(0)