云计算百科
云计算领域专业知识百科平台

【Linux网络加餐(一)】《读者写者问题与读写锁,自旋锁,网络命令,Windows 作为 UDP 客户端访问 Linux 服务器以及如何开放轻量级应用服务器》---详解

一.读者写者问题

这个问题是啥:

多个线程要读要写同一份数据,怎么协调不打架。

读的人之间没事,可以一起读。但只要有人要写,其他人(不管是读的还是写的)都得等着。

进行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.弄好是这个样子:

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux网络加餐(一)】《读者写者问题与读写锁,自旋锁,网络命令,Windows 作为 UDP 客户端访问 Linux 服务器以及如何开放轻量级应用服务器》---详解
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!