一.读者写者问题
这个问题是啥:
多个线程要读要写同一份数据,怎么协调不打架。
读的人之间没事,可以一起读。但只要有人要写,其他人(不管是读的还是写的)都得等着。
进行321原则:
3种关系
-
读者和读者:没关系,随便一起读
-
写者和写者:不能同时写,得排队
-
读者和写者:写的时候不能读,读的时候不能写
2种角色
-
读者:光看不改
-
写者:改数据
1个地方
- 共享的那块数据
那这个读写跟生产消费有啥不一样?
消费者会拿走数据,读者不会。
生产消费里,东西被取走了就没了。读者写者里,数据一直在那,谁都不动。
所以:
-
生产消费关心的是"有货没货"
-
读者写者关心的是"有没有人在写"
简单举个例子理解一下:
图书馆看书
- 多人同时看一本书 –>没问题
- 管理员要整理书 –> 等读者看完,整理完读者才能继续看
- 两个管理员同时整 –>不行,乱套
数据库
-
多个查询同时跑 –> 没问题
-
更新数据 –> 所有查询都得等
-
多个更新 –> 一个一个来
简单点理解就是:读读没事,读写有事,写写有事
1.理解读者写者问题
主要是理解读者和写者如何完成同步
我们先看一串伪代码:
// 公共资源
uint32_t reader_count = 0; // 当前有多少个读者在读
lock_t count_lock; // 保护 reader_count 的锁
lock_t writer_lock; // 写者专用的锁
// 读者
lock(count_lock); // ① 先锁住 count,准备修改读者数量
if(reader_count == 0) { // ② 如果我是第一个读者
lock(writer_lock); // ③ 锁住写者锁,不让写者进来
}
++reader_count; // ④ 读者数+1
unlock(count_lock); // ⑤ 释放 count 锁
// read; // ⑥ 读取数据(核心操作)
lock(count_lock); // ⑦ 再次锁住 count
–reader_count; // ⑧ 读者数-1
if(reader_count == 0) { // ⑨ 如果我是最后一个读者
unlock(writer_lock); // ⑩ 释放写者锁,让写者进来
}
unlock(count_lock); // ⑪ 释放 count 锁
// 写者
lock(writer_lock); // ① 锁住写者锁
// write; // ② 写入数据(核心操作)
unlock(writer_lock); // ③ 释放写者锁
公共资源(临界资源)三个东西:
reader_count:数着现在几个读者在读,0就是没人
count_lock:保护这个数,别让多个读者同时改它
writer_lock:写者的锁,谁拿着谁才能写
读者在干什么 读之前:
锁住计数器
如果是第一个读者,把写者锁拿走(写的人别来)
读者数+1
放开计数器
读之后:
锁住计数器
读者数-1
如果是最后一个读者,把写者锁还回去(写的人可以来了)
放开计数器
简单说就是:第一个读者拦写者,最后一个读者放写者,中间的读者啥都不用管。
接下来看写者:
拿锁–>写–>放锁,三件事,简单粗暴。
核心就是:读者之间随便读,但只要有人写,谁都得等着。
writer_lock 是关键,读者用它拦住写者,写者用它独占数据。第一个读者拿走,最后一个读者还回来,中间多少读者都不影响。
但这里有啥毛病?
写者可能饿死。如果读者一个接一个不停,写者永远拿不到 writer_lock,只能干等。这叫"读者优先",读者太霸道了。
2.读写锁
在编写多线程的时候,有⼀种情况是⼗分常⻅的。那就是,有些公共数据修改的机会⽐较少。相⽐较改写,它们读的机会反而高的多。通常⽽⾔,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极⼤地降低我们程序的效率。那么有没有⼀种⽅法,可以专⻔处理这种多读少写的情况呢?有,那就是读写锁。

注意:写独占,读共享,读锁优先级⾼
读写锁接口
a.属性设置(选哪种策略)
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
三种策略
三种策略
//1. 读者优先(默认)
//读者来了就读,写者等所有读者完事才能写。问题:读者太多写者可能饿死。
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_READER_NP);
//2. 写者优先(有BUG)
//名字叫写者优先,实际效果跟读者优先一样,没用。跳过。
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NP); // 别用
//3. 真·写者优先
//写者来了插队,读者得等着。限制:同一个线程不能重复加写锁(不能递归)。
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
b.初始化和销毁
// 初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
// 销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);


c.加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 解锁(读锁写锁都用这个解)
//Makefile
reader_writer_lock_test:reader_writer_lock_test.cc
g++ -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f reader_writer_lock_test
//reader_writer_lock_test.cc
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
#include<cstdlib>
#include<ctime>
//共享资源
int shared_data = 0;
//读写锁
pthread_rwlock_t rwlock;
//读写线程函数
void *Reader(void *arg)
{
//sleep(1);//因为读者优先,一旦读者进入&&读者很多,写者基本就真难进入了
int number = *(int*)arg;
while(true)
{
pthread_rwlock_rdlock(&rwlock);//读者加锁
std::cout<<"读者–"<<number<<"正在加载数据,数据是:"<<shared_data<<std::endl;//模拟读取操作
sleep(1);
pthread_rwlock_unlock(&rwlock);//解锁
}
delete (int*)arg;
return NULL;
}
//写者进程函数
void *Writer(void *arg)
{
int number = *(int*)arg;
while(true)
{
pthread_rwlock_wrlock(&rwlock);//写者加锁
shared_data = rand() % 100;//修改共享数据
std::cout<<"写者–"<<number<<"正在写入.新的数据是:"<<shared_data<<std::endl;
sleep(2);//模拟写入操作
pthread_rwlock_unlock(&rwlock);
}
delete (int *)arg;
return NULL;
}
int main()
{
srand(time(nullptr)^getpid());
pthread_rwlock_init(&rwlock,NULL);//初始化写锁
//可以提高读写数量的配比,观察现象
const int reader_num = 2;
const int writer_num = 2;
const int total = reader_num+writer_num;
pthread_t threads[total];//假设读者和写者数量相等
//创建读者线程
for(int i = 0;i<reader_num;i++)
{
int *id = new int(i);
pthread_create(&threads[i],NULL,Reader,id);
}
//创建写者线程
for(int i = reader_num;i<total;i++)
{
int *id = new int(i – reader_num);
pthread_create(&threads[i],NULL,Writer,id);
}
//等待所有的线程完成
for(int i = 0;i<total;++i)
{
pthread_join(threads[i],NULL);
}
pthread_rwlock_destroy(&rwlock);//销毁读写锁
return 0;
}
thread_create(…, Reader, id) 里的 id 就是 arg:
// main里传参
int *id = new int(i);
pthread_create(&threads[i], NULL, Reader, id);
// 就是arg
// Reader里收参
void *Reader(void *arg) // arg就是传进来的id
{
int number = *(int*)arg; // 转回int,取值
}
为啥用 new:局部变量等不到线程执行就没了,堆上分配才安全。用完记得 delete。

那如果读写者数量差的比较大呢?看结果

我们会发现结果更明显
3.读写优先策略
读者优先(Reader-Preference)
在这种策略中,系统会优先考虑写者。当写者请求写⼊权限时,系统会尽快地让写者进⼊写⼊区,即使此时有读者正在读取。这通常意味着⼀旦有写者到达,所有后续的读者都会被阻塞,直到写者完成写⼊并离开写⼊区。写者优先策略可以减少写者等待的时间,但可能会导致读者饥饿(即读者⻓时间⽆法获得读取权限),特别是当写者频繁到达时。
简单说就是,读者来了就读,写者靠边等。最后一个读者走的时候才放写者进来。
好处:读者体验好,不卡顿 坏处:写者可能永远轮不上,活活饿死
写者优先 (Writer-Preference)
在这种策略中,系统会优先考虑写者。当写者请求写⼊权限时,系统会尽快地让写者进⼊写⼊区,即使此时有读者正在读取。这通常意味着⼀旦有写者到达,所有后续的读者都会被阻塞,直到写者完成写⼊并离开写⼊区。写者优先策略可以减少写者等待的时间,但可能会导致读者饥饿(即读者⻓时间⽆法获得读取权限),特别是当写者频繁到达时。
简单说就是,写者来了就插队,新来的读者得等着。写者干完活读者才能进。
好处:写者不用等太久 坏处:读者可能永远轮不上,活活饿死
公平策略
排队轮着来,先到先得,不分读者写者。
好处:谁都不会饿死 坏处:并发性能没上面两个高
二.自旋锁
1.概述
⾃旋锁是⼀种多线程同步机制,⽤于保护共享资源免受并发访问的影响。在多个线程尝试获取锁时,它们会持续⾃旋(即在⼀个循环中不断检查锁是否可⽤)⽽不是⽴即进⼊休眠状态等待锁的释放。这种机制减少了线程切换的开销,适⽤于短时间内锁的竞争情况。但是不合理的使⽤,可能会造成CPU的浪费。
现实生活中的例子
打电话占线
你给朋友打电话,对方正在通话中。这时候你有两种选择:
挂断等会儿再打(互斥锁)–> 不占电话线,但得重新拨号;
不挂断,一直听忙音等(自旋锁)–> 占着电话线,但对方一挂你立马就能接通
自旋锁就是第二种:不放弃CPU,一直在那转圈等。

2.原理
⾃旋锁通常使⽤⼀个共享的标志位(如⼀个布尔值)来表⽰锁的状态。当标志位为true时,表⽰锁已被某个线程占⽤;当标志位为false时,表⽰锁可⽤。当⼀个线程尝试获取⾃旋锁时,它会不断检查标志位:
- 如果标志位为false,表⽰锁可⽤,线程将设置标志位为true,表⽰⾃⼰占⽤了锁,并进⼊临界区。
- 如果标志位为true(即锁已被其他线程占⽤),线程会在⼀个循环中不断⾃旋等待,直到锁被释放。
3.自旋锁优缺点
a.优点
- 响应快不用等系统调度,锁一释放马上就能抢到,不磨叽。
- 开销小不用让线程挂起再唤醒,省掉了上下文切换的成本。
b.缺点
- 费CPU拿不到锁的线程一直在那转圈等,白白消耗CPU时间片。如果锁被占的时间长,CPU就一直在空转。
- 可能死循环多个线程同时转圈等同一个锁,如果没人让一步,可能所有线程都在那干瞪眼,谁也进不去。
4.适用场景
- 锁占用的时间极短比如就改个计数器、标记个状态,几行代码的事。
- 底层系统编程多核CPU之间抢共享资源的时候用得多,应用层很少碰这玩意儿。
自旋锁就是"等的时候不睡觉,原地转圈盯着看"。锁等的时间短就用它,时间长就别用,浪费CPU
5.纯软件⾃旋锁类似的原理实现

自旋锁的两种实现方式
方式一:⾃旋锁的实现通常使⽤原⼦操作来保证操作的原⼦性,常⽤的软件实现⽅式是通过CAS(Compare-And-Swap)指令实现。
那atomic_flag 是啥?说白了就是个标志位,只能存0或1,专门用来做自旋锁那种简单的锁。
来看底层:
typedef _Atomic struct {
// GCC编译器用这个
#if __GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1
_Bool __val; // 就是个布尔值
#else
unsigned char __val; // 其他编译器用这个
#endif
} atomic_flag;
- _Atomic 关键字
告诉编译器:"这个变量的操作要原子化,不能被打断"。
- atomic_flag_test_and_set
干两件事:看一眼当前值,然后把它设成1。这两步是绑在一起的,中间不会被别人插队。
- atomic_flag_clear
直接清成0。
实际用起来就这几行
atomic_flag lock = ATOMIC_FLAG_INIT; // 一开始是0(没锁)
// 加锁:如果锁是0就抢到并改成1,如果是1就转圈等
while (atomic_flag_test_and_set(&lock)) {
// 忙等待
}
// 解锁:直接清0
atomic_flag_clear(&lock);
代码如下(这是一个伪代码):
#include <stdatomic.h>
atomic_flag spinlock = ATOMIC_FLAG_INIT; // 初始状态:0(未锁)
// 加锁
void spinlock_lock() {
while (atomic_flag_test_and_set(&spinlock)) {
// 忙等待:锁被占用就一直转圈
}
}
// 解锁
void spinlock_unlock() {
atomic_flag_clear(&spinlock); // 直接清0
}
atomic_flag_test_and_set 干啥的?
第一次调用:spinlock 是 0 –> 设置成 1,返回 0 –> 跳出循环,拿到锁
别人调用:spinlock 是 1 –> 返回 1,不修改 –> 继续循环,拿不到锁
解锁后:spinlock 清成 0,别人就能拿到了
简单说就是,如果锁是 0 就抢到并改成 1,如果是 1 就转圈等。整个过程是原子的,不会被打断。
方式二:pthread 提供的自旋锁
#include <pthread.h>
pthread_spinlock_t lock;
// 使用流程
pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE); // 初始化
pthread_spin_lock(&lock); // 加锁(拿不到就转圈)
pthread_spin_trylock(&lock); // 尝试加锁(拿不到就返回错误,不转圈)
pthread_spin_unlock(&lock); // 解锁
pthread_spin_destroy(&lock); // 销毁
两种的话,讲究性价比的话,自己写的是为了看明白原理,实际开发直接用 pthread_spin_lock 这些函数,省事还靠谱。
来看完整代码:
//Makefile
reader_writer_lock_test:reader_writer_lock_test.cc
g++ -o $@ $^ -lpthread
spin_lock:spin_lock.cc
g++ -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f reader_writer_lock_test spin_lock
//spin_lock.cc
//操作共享变量会有售票的问题
#include<iostream>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int ticket = 1000;
//pthread_spinlock_t lock;
void *route(void *arg)
{
char *id = (char*)arg;
while(1){
pthread_spin_lock(&lock); // 加锁
if(ticket>0){
usleep(1000);
printf("%s sell tockets%d\\n",id,ticket);
ticket–;
//pthread_spin_unlock(&lock);
}
else
{
//pthread_spin_unlock(&lock);
break;
}
}
return nullptr;
}
int main()
{
//pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
pthread_t t1,t2,t3,t4;
pthread_create(&t1,NULL,route,(void *)"thread 1");
pthread_create(&t2, NULL, route, (void *)"thread 2");
pthread_create(&t3, NULL, route, (void *)"thread 3");
pthread_create(&t4, NULL, route, (void *)"thread 4");
pthread_join(t1,NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
//pthread_spin_destroy(&lock);
}

出现-1了,如果知道为什么,就看我这篇博客里面很详细的讲解:【Linux】二十七.线程篇四《Linux多线程编程:线程互斥(互斥量的底层到封装)、线程安全和冲入、死锁》—详解-CSDN博客
我们现在解开锁再看:
不会出现负数了,正常显示:

自旋锁注意事项
持锁时间要短 锁占用时间越短越好,不然CPU一直空转,浪费资源。
单核CPU别用 等待的线程会跟持锁的线程抢CPU,反而拖慢锁释放,越等越慢。
锁和unlock必须成对 有lock就要有unlock,少一个就死锁。
持锁期间不能睡眠 sleep、wait这些都不能用,否则其他线程干等着,CPU也空转。
自旋锁优缺点:
a.优点:
1. 响应快 锁一释放就能立刻抢到,不用等系统调度。没有挂起唤醒那套流程,反应速度杠杠的。
2. 开销小 不用让线程挂起再唤醒,省掉了上下文切换的开销。对那种几行代码就能完事的操作来说,自旋等待的成本比切换线程低得多。
b.缺点
1. CPU资源浪费 拿不到锁的线程一直在那转圈,啥也不干光消耗CPU。锁占用时间长的话,CPU时间片就白白浪费了。单核CPU上更惨,等待的线程跟持锁的线程抢CPU,反而拖慢锁释放。
2. 可能引发活锁 多个线程一起转圈等同一个锁,如果调度不当,可能都在检查锁状态但谁也进不去。线程没阻塞,但也没进展,看似忙实际啥也没干成。
三. 网络命令
1.Ping 命令
基本用法:
ping www.qq.com # 一直 ping,按 Ctrl+C 停止
ping -c 5 www.qq.com # ping 5 次后自动停止
作用:
ping 就是测网络通不通、快不快的工具。
三个指标:
通不通:有没有回复,有没有丢包 快不快:time 值,越小越快 稳不稳:丢包率,0% 最稳
执行结果:
PING ins-r23tsuuf.ias.tencent-cloud.net (121.14.77.221) 56(84) bytes of data.
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=1 ttl=48 time=35.1 ms
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=2 ttl=48 time=35.1 ms
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=3 ttl=48 time=35.1 ms
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=4 ttl=48 time=35.1 ms
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=5 ttl=48 time=35.1 ms
— ins-r23tsuuf.ias.tencent-cloud.net ping statistics —
5 packets transmitted, 5 received, 0% packet loss, time 4005ms
表示:通了,没丢包,延迟 35ms,网络质量良好。
| ins-r23tsuuf.ias.tencent-cloud.net | 域名解析后的服务器名 |
| 121.14.77.221 | 服务器的 IP 地址 |
| 56(84) bytes | 发了 56 字节数据,加上包头共 84 字节 |
| icmp_seq=1 | 第 1 个包(序号) |
| ttl=48 | 还剩 48 跳,说明经过了 16~80 个路由器 |
| time=35.1 ms | 网络延迟 35.1 毫秒(越小越快) |
| 5 packets transmitted | 发了 5 个包 |
| 5 received | 收到 5 个回复 |
| 0% packet loss | 没丢包(网络稳定) |
| time 4005ms | 总耗时约 4 秒 |
2.netstat 命令
netstat是⼀个⽤来查看⽹络状态的重要⼯具,比如端口开了没有、谁在连你、连接多不多。
语法:
netstat [选项]
功能:
查看⽹络状态
常用选项
| -n | 显示IP和端口号,不解析成域名(数字比名字快) |
| -l | 只看正在监听的服务(等别人来连的) |
| -p | 显示占用端口的程序名称 |
| -t | 只看 TCP 连接 |
| -u | 只看 UDP 连接 |
| -a | 显示所有连接(默认不显示监听中的) |
常用组合
netstat -nltp # 看所有TCP监听端口,显示数字IP,带程序名
netstat -nlup # 看所有UDP监听端口
netstat -an # 看所有连接
-nltp 输出示例
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1180/sshd
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 1456/mysqld
tcp6 0 0 :::80 :::* LISTEN 2034/nginx
| Proto | 协议(tcp/udp) |
| Local Address | 本机监听的IP和端口 |
| Foreign Address | 远端地址(监听状态显示*) |
| State | 状态(LISTEN表示正在等连接) |
| PID/Program | 进程ID和程序名 |
定时查看
watch -n 1 netstat -nltp # 每隔1秒刷新一次
快速判断
netstat -nltp | grep 80 # 看80端口被谁占了
netstat -nltp | grep LISTEN # 看开了哪些服务
netstat 就是看网络连接和监听端口的工具,-nltp 是最常用的组合。
3.pidof
在查看服务器的进程id时⾮常⽅便. 语法:
pidof [进程名]
功能:
通过进程名, 查看进程id
通过进程名查进程ID,比 ps + grep 快得多。
$ps axj | head -1 && ps ajx | grep tcp_server
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2958169 2958285 2958285 2958169 pts/2 2958285 S+ 1002 0:00 ./tcp_server 8888
| PPID | 父进程ID |
| PID | 进程ID(2958285) |
| PGID | 进程组ID |
| SID | 会话ID |
| TTY | 终端 |
| STAT | 状态(S+ 表示睡眠+前台) |
| UID | 用户ID |
| TIME | CPU时间 |
| COMMAND | 命令 |
a.用 ps 查
ps axj | head -1 && ps ajx | grep tcp_server
输出一堆信息,还得自己找PID。
b.用 pidof 查
pidof tcp_server
# 输出:2958285
干净利落,只给ID。
常用场景
pidof nginx # 查nginx主进程ID
pidof mysqld # 查mysql进程ID
pidof sshd # 查ssh服务ID
kill -9 $(pidof tcp_server) # 直接杀掉进程
注意如果有多个同名进程,pidof 会显示所有ID:
pidof nginx
# 输出:2034 2033 2032 (多个进程)
四.验证UDP – windows 作为client访问Linux
学习这个加餐课内容,可以先看一下这篇博客,是它的前提:【Linux】三十二.《Linux 网络编程:UDP Socket 编程深度梳理:IP 端口、网络字节序、socket API,手写多线程聊天室巩固网络模型》-CSDN博客
来看代码:
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>
#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")
std::string serverip = ""; // 填写你的云服务器ip
uint16_t serverport = 8888; // 填写你的云服务开放的端口号
int main()
{
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd);
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport); //?
server.sin_addr.s_addr = inet_addr(serverip.c_str());
SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == SOCKET_ERROR)
{
std::cout << "socker error" << std::endl;
return 1;
}
std::string message;
char buffer[1024];
while (true)
{
std::cout << "Please Enter@ ";
std::getline(std::cin, message);
if (message.empty()) continue;
sendto(sockfd, message.c_str(), (int)message.size(), 0, (struct sockaddr*)&server, sizeof(server));
struct sockaddr_in temp;
int len = sizeof(temp);
int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
if (s > 0)
{
buffer[s] = 0;
std::cout << buffer << std::endl;
}
}
closesocket(sockfd);
WSACleanup();
return 0;
}

- 注意:⼀定要开放云服务器对应的端⼝号,在你的阿⾥云或者腾讯云或者华为云的⽹站后台中开放。
- 我们发现可以udp tcpclient(Windows)和tcpserver(Linux)可以通信
大致内容如下:
WinSock2.h 是啥?
Windows 下网络编程的头文件,socket 编程就靠它。
使用前要准备
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") // 对应的库文件
头文件告诉编译器有啥东西,库文件告诉链接器去哪儿找实现,两个都得有。
主要的东西
- WSADATA:存 Winsock 初始化信息
- SOCKET:套接字,网络通信就靠它
- sockaddr_in:存 IP 和端口
- socket():创建套接字
- bind():绑定地址端口
- listen():监听
- accept():接受连接
WSAStartup:必须第一个调
在调任何 socket 函数之前,必须先初始化:
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData); // 用 Winsock 2.2 版本
两个参数:
- MAKEWORD(2, 2):告诉系统我要用 2.2 版本
- &wsaData:系统会填些信息进去
成功返回 0,失败返回错误码。
WSACleanup:用完记得收尾
MAKEWORD(2, 2):告诉系统我要用 2.2 版本
&wsaData:系统会填些信息进去
流程如下:
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main() {
WSADATA wsaData;
// 第一步:初始化
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 第二步:用 socket、bind、listen、accept 干活
// 第三步:清理
WSACleanup();
}
Windows 写网络程序,先 WSAStartup 初始化,最后 WSACleanup 收尾。 这套流程固定,写啥网络程序都逃不掉。
五.如何开放轻量级应用服务器
1.我装的腾讯云,点击控制台

2.点击轻量级应用服务器:



3.添加规则:

4.弄好是这个样子:

网硕互联帮助中心





评论前必须登录!
注册