最后的话
最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!
资料预览
给大家整理的视频资料:
给大家整理的电子书资料:
如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
3)常用函数
创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
成功返回套接字描述符,失败返回-1。
domain - 通信域,即协议族,可取以下值:
PF\_LOCAL/PF\_UNIX: 本地通信,进程间通信
PF\_INET: 互联网通信
PF\_PACKET: 底层包通信(嗅探器、端口扫描)
type - 套接字类型,可取以下值:
SOCK\_STREAM: 流式套接字,使用TCP协议
SOCK\_DGRAM: 数据报式套接字,使用UDP协议
SOCK\_RAW: 原始套接字,使用自定义协议
protocol - 特殊协议
对于流式套接字和数据报式套接字,取0
套接字描述符与文件描述符在逻辑层面是一致的,所有关于文件描述符的规则对于套接字描述符也同样成立。同样也通过close函数关闭套接字,即释放内核中的有关资源。
基本地址结构:
struct sockaddr {
sa\_family\_t sa\_family; // 地址族
char sa\_data[14]; // 地址值
};
基本地址结构仅用于给函数传参时做强制类型转换。
本地地址结构:
#include <sys/un.h>
struct sockaddr\_un {
sa\_family\_t sun\_family; // 地址族
// (AF\_LOCAL/AF\_UNIX)
char sun\_path[]; // 套接字文件路径
};
网络地址结构:
#include <sys/in.h>
struct sockaddr\_in {
sa\_family\_t sin\_family; // 地址族(AF\_INET)
in\_port\_t sin\_port; // 端口号(网络字节序)
struct in\_addr sin\_addr; // IP地址
};
struct in\_addr {
in\_addr\_t s\_addr; // 网络字节序32位无符号整数形式的
// IP地址
};
typedef uint32\_t in\_addr\_t;
typedef uint16\_t in\_port\_t;
小端字节序的主机A 大端字节序的主机B
short a = 0x1234;
| 0x34 | 0x12 | | 0x34 | 0x12 |
低 高 低 高
a: 0x3412
发送:主机字节序->网络(大端)字节序
接收:网络(大端)字节序->主机字节序
小端字节序的主机A 大端字节序的主机B
0x1234
主:0x34 | 0x12
L H
网:0x12 | 0x34 -> 网:0x12 | 0x34
L H
主:0x12 | 0x34
0x1234
--------------------
小端字节序的主机C
-> 网:0x12 | 0x34
L H
主:0x34 | 0x12
0x1234
将套接字对象和自己的地址结构绑定在一起
int bind(int sockfd, const struct sockaddr\* addr,
socklen\_t addrlen);
成功返回0,失败返回-1。
sockfd - 套接字描述符
addr - 自己的地址结构
addrlen - 地址结构字节数
addr->sa\_family: AF\_LOCAL/AF\_UNIX
((struct sockaddr\_un\*)addr)->sun\_path: 套接字文件
addr->sa\_family: AF\_INET
((struct sockaddr\_in\*)addr)->sin\_port/sin\_addr:
IP地址和端口号
将套接字对象所代表的物理对象和对方的地址结构连接在一起
int connect(int sockfd, const struct sockaddr\* addr,
socklen\_t addrlen);
成功返回0,失败返回-1。
sockfd - 套接字描述符
addr - 对方的地址结构
addrlen - 地址结构字节数
通过套接字描述符接收和发送数据的过程完全与通过文件描述符读取和写入数据的过程完全一样。
ssize\_t read(int sockfd, void\* buf, size\_t count);
ssize\_t write(int sockfd, const void\* buf, size\_t count);
字节序转换
通过网络传输多字节整数,需要在发送前转换为网络字节序,在接收后转换为主机字节序。
#include <arpa/inet.h>
uint32\_t htonl(uint32\_t hostlong);
uint16\_t htons(uint16\_t hostshort);
uint32\_t ntohl(uint32\_t netlong);
uint16\_t ntohs(uint16\_t netshort);
h - host,主机(字节序)
to - 到,把…转换到…
n - network,网络(字节序)
l - long版本,32位无符号整数
s - short版本,16位无符号整数
IP地址转换
(网络字节序32位无符号)整数<=>(点分十进制)字符串
#include <arpa/inet.h>
in\_addr\_t inet\_addr(const char\* cp); // 串->数
int inet\_aton(const char\* cp, struct in\_addr\* inp);
// 串->数
转换成功返回0,失败返回-1。
char\* inet\_ntoa(struct in\_addr in); // 数->串
转换成功返回字符串指针,失败返回NULL。
基于本地套接字的进程间通信:
服务器:提供业务服务的计算机程序。
客户机:请求业务服务的计算机程序。
服务器 客户机
创建套接字(socket) 创建套接字(socket)
准备地址结构(sockaddr_un) 准备地址结构(sockaddr_un)
绑定地址(bind) 建立连接(connect)
接收请求(read) 发送请求(write)
业务处理(…) 等待处理(…)
发送响应(write) 接收响应(read)
关闭套接字(close) 关闭套接字(close)
代码:locsvr.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCK_FILE “mysock”
int main(void) {
printf(“服务器:创建本地套接字\n”);
int sockfd = socket(PF_LOCAL, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“服务器:准备地址并绑定\n”);
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, SOCK_FILE);
if (bind(sockfd, (struct sockaddr*)&addr,
sizeof(addr)) == -1) {
perror(“bind”);
return -1;
}
printf(“服务器:接收数据\n”);
for (;😉 {
char buf[1024] = {};
ssize_t rb = read(sockfd, buf, sizeof(
buf) - sizeof(buf[0]));
if (rb == -1) {
perror(“read”);
return -1;
}
if (!strcmp(buf, “!\n”))
break;
printf(“< %s”, buf);
}
printf(“服务器:关闭套接字\n”);
if (close(sockfd) == -1) {
perror(“close”);
return -1;
}
printf(“服务器:删除套接字文件\n”);
if (unlink(SOCK_FILE) == -1) {
perror(“unlink”);
return -1;
}
printf(“服务器:完成\n”);
return 0;
}
代码:loccli.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCK_FILE “mysock”
int main(void) {
printf(“客户机:创建本地套接字\n”);
int sockfd = socket(PF_LOCAL, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“客户机:准备地址并连接\n”);
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, SOCK_FILE);
if (connect(sockfd, (struct sockaddr*)&addr,
sizeof(addr)) == -1) {
perror(“connect”);
return -1;
}
printf(“客户机:发送数据\n”);
for (;😉 {
printf("> ");
char buf[1024];
fgets(buf, sizeof(buf) / sizeof(buf[0]),
stdin);
if (write(sockfd, buf, strlen(buf) *
sizeof(buf[0])) == -1) {
perror(“write”);
return -1;
}
if (!strcmp(buf, “!\n”))
break;
}
printf(“客户机:关闭套接字\n”);
if (close(sockfd) == -1) {
perror(“close”);
return -1;
}
printf(“客户机:完成\n”);
return 0;
}
基于网络套接字的进程间通信:
服务器 客户机
创建套接字(socket) 创建套接字(socket)
准备地址结构(sockaddr_in) 准备地址结构(sockaddr_in)
绑定地址(bind) 建立连接(connect)
接收请求(read) 发送请求(write)
业务处理(…) 等待处理(…)
发送响应(write) 接收响应(read)
关闭套接字(close) 关闭套接字(close)
代码:netsvr.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, “用法:%s <端口号>\n”,
argv[0]);
return -1;
}
printf(“服务器:创建网络套接字\n”);
int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“服务器:准备地址并绑定\n”);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr*)&addr,
sizeof(addr)) == -1) {
perror(“bind”);
return -1;
}
printf(“服务器:接收数据\n”);
for (;😉 {
char buf[1024] = {};
ssize_t rb = read(sockfd, buf, sizeof(
buf) - sizeof(buf[0]));
if (rb == -1) {
perror(“read”);
return -1;
}
if (!strcmp(buf, “!\n”))
break;
printf(“< %s”, buf);
}
printf(“服务器:关闭套接字\n”);
if (close(sockfd) == -1) {
perror(“close”);
return -1;
}
printf(“服务器:完成\n”);
return 0;
}
代码:netcli.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char* argv[]) {
if (argc < 3) {
fprintf(stderr, “用法:%s <IP地址> "
“<端口号>\n”, argv[0]);
return -1;
}
printf(“客户机:创建网络套接字\n”);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“客户机:准备地址并连接\n”);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr*)&addr,
sizeof(addr)) == -1) {
perror(“connect”);
return -1;
}
for (;😉 {
printf(”> “);
char buf[1024];
fgets(buf, sizeof(buf) / sizeof(buf[0]),
stdin);
if (!strcmp(buf, “!\n”))
break;
printf(“客户机:发送请求\n”);
if (send(sockfd, buf, strlen(buf) *
sizeof(buf[0]), 0) == -1) {
perror(“send”);
return -1;
}
printf(“客户机:接收响应\n”);
ssize_t rb = recv(sockfd, buf,
sizeof(buf) - sizeof(buf[0]), 0);
if (rb == 0) {
printf(“客户机:服务器已关闭\n”);
break;
}
buf[rb / sizeof(buf[0])] = ‘\0’;
printf(”< %s", buf);
}
printf(“客户机:关闭套接字\n”);
if (close(sockfd) == -1) {
perror(“close”);
return -1;
}
printf(“完成\n”);
return 0;
}
### 3.基于并发的TCP客户机/服务器模型
1)TCP协议的基本特征
A.面向连接:参与通信的双发在正式通信之前需要先建立连接,已形成一条虚拟电路,所有的后续通信都在这条虚电路上完成。类似于电话通信业务。正式通话之前要先拨号,拨通了才能讲话。拨号的过程就是一个建立连接的过程。
三路握手
客户机 服务器
发起连接请求 --------SYN(n)--------> 被动侦听
等待应答 <-ACK(n+1)+SYN(m)- 可以接受
反向确认 ------ACK(m+1)------> 连接成功
一旦三路握手完成,客户机和服务器的网络协议栈中就会保存有关连接的信息,此后的通信内容全部基于此连接实现数据传输。通信过程中任何原因导致的连接中断,都无法再继续通信,除非重新建立连接。
B.可靠传输:超时重传。每次发送一个数据包,对方都需要在一个给定的时间窗口内予以应答,如果超过时间没有收到对方的应答,发送方就会重发该数据包,只有重试过足够多的次数依然失败才会最终放弃。
C.保证顺序:发送端为每一个数据包编制序列号,接收端会根据序列号对所接收到的数据包进行重排,避免重复和乱序。
D.流量控制:协议栈底层在从另一端接收数据时,会不断告知对方它能够接收多少字节的数据,即所谓通告窗口。任何时候,这个窗口都反映了接收缓冲区可用空间的大小,从而确保不会因为发送方发送数据过快或过慢导致接收缓冲区出现上溢出或下溢出。
E.流式传输:以字节流形式传输数据,数据包在传输过程中没有记录边界。应用程序需要根据自己的规则来划分出数据包的记录边界。
a)定长记录
b)不定长记录加分隔符
c)定长长度加不定长记录
F.全双工:在给定的连接上,应用程序在任何时候都既可以发送数据也可以接收数据。因此TCP必须跟踪每个方向上数据流的状态信息,如序列号和通告窗口大小等。
2)TCP连接的生命周期
被动打开:通过侦听套接字感知其它主机发起的连接请求。
三路握手:TCP连接的建立过程。
| TCP包头 | TCP包体 |
<20字节>
含有6个标志位:SYN/ACK/FIN/RST/…
发送序列号和应答序列号
…
数据传输:超时重传、流量控制、面向字节流、全双工
终止连接:
客户机 服务器
主动关闭 ---------FIN(n)-------> 被动关闭
等待应答 <-----ACK(n+1)------ 关闭应答
确定关闭 ---------FIN(m)------> 已经关闭
关闭应答 ------ACK(m+1)-----> 等待应答
3)常用函数
在指定套接字上启动对 `连接请求`的侦听,即将该套接字置为被动模式,因为套接字都缺省为主动模式。
int listen(int sockfd, int backlog);
成功返回0,失败返回-1。
sockfd - 套接字描述符
backlog - 未决连接请求队列的最大长度
listen之后socketfd就有监听 连接请求 功能了。
在指定的侦听套接字上等待并接受连接请求
int accept(int sockfd, struct sockaddr\* addr,
size\_t\* addrlen);
成功返回连接套接字描述符用于后续通信,失败返回-1。
sockfd - 侦听套接字描述符
addr - 输出连接请求发起者的地址信息
addrlen - 输入输出连接请求发起者地址信息的字节数
该函数由TCP服务器调用,返回排在已决连接队列首部的连接套接字对象的描述符,若已决连接队列为空,该函数会阻塞。
`--------------------------------------------------------`
非并发的TCP服务器
创建套接字(socket)
绑定地址(bind)
启动侦听(listen)
等待连接(accept)<----+
接收请求(read)<–+ |
业务处理(…) | |
发送响应(write)–±–+
并发的TCP服务器
创建套接字(socket)
绑定地址(bind)
启动侦听(listen)
等待连接(accept)<----+
|
产生客户子进程(fork)–+
接收请求(read)<–+
业务处理(…) |
发送响应(write)–+
接收数据
ssize\_t recv(int sockfd, void\* buf, size\_t len, int flags);
flags - 接收标志,取0等价于read
MSG\_DONTWAIT: 非阻塞接收
对于阻塞模式,当接收缓冲区为空时,该函数会阻塞,直到
接收缓冲区不空为止。如果使用了此标志位,当接收缓冲区
为空时,该函数会返回-1,并置errno为EAGAIN或
EWOULDBLOCK。
MSG\_OOB: 接收带外数据。
MSG\_PEEK: 瞄一眼数据,只讲接收缓冲区中的数据复制到 buf缓冲区中,但并不将其从接收缓冲区中删除。
MSG\_WAITALL: 接收到所有期望接收的数据才返回,如果接收缓冲区中的数据不到len个字节,该函数会阻塞,直到接收到len个字节为止。
发送数据
ssize\_t send(int sockfd, const void\* buf, size\_t len,
int flags);
flags - 接收标志,取0等价于write
MSG\_DONTWAIT: 非阻塞发送
对于阻塞模式,当发送缓冲区的空余空间不足以容纳期望发送的字节数时,该函数会阻塞,直到发送缓冲区的空余空间足以容纳期望发送的字节数为止。如果使用了此标志位,能发送多少字节就发送多少字节,不会阻塞,甚至可能返回0,表示发送缓冲区满,无法发送数据。
MSG\_OOB: 发送带外数据。
MSG\_DONTROUT: 不查路由表,直接在本地网中寻找目的主机。
代码:tcpsvr.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void sigchld(int signum) {
for (;😉 {
pid_t pid = waitpid(-1, NULL, WNOHANG);
if (pid == -1) {
if (errno != ECHILD) {
perror(“waitpid”);
exit(-1);
}
printf(“服务器:”
“全部子进程都已退出\n”);
break;
}
if (pid)
printf(“服务器:”
“发现%d子进程退出\n”, pid);
else {
printf(“服务器:”
“暂时没有子进程退出\n”);
break;
}
}
}
int client(int connfd) {
for (;😉 {
printf(“%d:接收请求\n”, getpid());
char buf[1024];
ssize_t rb = recv(connfd, buf,
sizeof(buf), 0);
if (rb == -1) {
perror(“recv”);
return -1;
}
if (rb == 0) {
printf(“%d:客户机已关闭\n”,
getpid());
break;
}
printf(“%d:发送响应\n”, getpid());
if (send(connfd, buf, rb, 0) == -1) {
perror(“send”);
return -1;
}
}
return 0;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, “用法:%s <端口号>\n”,
argv[0]);
return -1;
}
if (signal(SIGCHLD, sigchld) == SIG_ERR) {
perror(“signal”);
return -1;
}
printf(“服务器:创建网络套接字\n”);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“服务器:准备地址并绑定\n”);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr*)&addr,
sizeof(addr)) == -1) {
perror(“bind”);
return -1;
}
printf(“服务器:侦听套接字\n”);
if (listen(sockfd, 1024) == -1) {
perror(“listen”);
return -1;
}
for (;😉 {
printf(“服务器:等待连接\n”);
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof(addrcli);
int connfd = accept(sockfd,
(struct sockaddr*)&addrcli,
&addrlen);
if (connfd == -1) {
perror(“accept”);
return -1;
}
printf(“服务器:客户机%s:%u\n”,
inet_ntoa(addrcli.sin_addr),
ntohs(addrcli.sin_port));
pid_t pid = fork();
if (pid == -1) {
perror(“fork”);
return -1;
}
if (pid == 0) {
if (close(sockfd) == -1) {
perror(“close”);
return -1;
}
if (client(connfd) == -1)
return -1;
if (close(connfd) == -1) {
perror(“close”);
return -1;
}
printf(“%d:完成\n”, getpid());
return 0;
}
if (close(connfd) == -1) {
perror(“close”);
return -1;
}
}
return 0;
}
代码:tcpcli.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char* argv[]) {
if (argc < 3) {
fprintf(stderr, “用法:%s <IP地址> "
“<端口号>\n”, argv[0]);
return -1;
}
printf(“客户机:创建网络套接字\n”);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“客户机:准备地址并连接\n”);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr*)&addr,
sizeof(addr)) == -1) {
perror(“connect”);
return -1;
}
for (;😉 {
printf(”> “);
char buf[1024];
fgets(buf, sizeof(buf) / sizeof(buf[0]),
stdin);
if (!strcmp(buf, “!\n”))
break;
printf(“客户机:发送请求\n”);
if (send(sockfd, buf, strlen(buf) *
sizeof(buf[0]), 0) == -1) {
perror(“send”);
return -1;
}
printf(“客户机:接收响应\n”);
ssize_t rb = recv(sockfd, buf,
sizeof(buf) - sizeof(buf[0]), 0);
if (rb == 0) {
printf(“客户机:服务器已关闭\n”);
break;
}
buf[rb / sizeof(buf[0])] = ‘\0’;
printf(”< %s", buf);
}
printf(“客户机:关闭套接字\n”);
if (close(sockfd) == -1) {
perror(“close”);
return -1;
}
printf(“完成\n”);
return 0;
}
### 4.基于迭代的UDP客户机/服务器模型
1)UDP协议的基本特征
A.面向无连接:参与通信的主机之间不需要专门建立和维护逻辑的连接通道。一个UDP套接字可以和任意其它UDP套接字通信,而不必受连接的限制。
B.不可靠传输:没有超时重传机制。可能导致数据丢失。
C.不保证顺序:没有序列号,也不进行序列重组。可能产生数据包重复和乱序。
D.无流量控制:没有通告窗口,通信参与者完全不知道对方的接受能力。可能造成数据溢出。
E.记录式传输:以消息报文的形式传输数据,数据包在传输过程中有记录边界。应用程序无需划分数据包边界。
F.全双工
通过在可靠性方面的部分牺牲换取高速度。
2)常用函数
ssize\_t sendto(int sockfd, const void\* buf, size\_t len,
int flags, const struct sockaddr\* dest\_addr,
socklen\_t addrlen);
dest\_addr - 数据包接收者地址结构
addrlen - 数据包接收者地址结构字节数
ssize\_t recvfrom(int sockfd, void\* buf, size\_t len,
int flags, struct sockaddr\* src\_addr,
socklen\_t\* addrlen);
src\_addr - 输出数据包发送者地址结构
addrlen - 输出数据包发送者地址结构字节数
代码:udpsvr.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, “用法:%s <端口号>\n”,
argv[0]);
return -1;
}
printf(“服务器:创建网络套接字\n”);
int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“服务器:准备地址并绑定\n”);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr*)&addr,
sizeof(addr)) == -1) {
perror(“bind”);
return -1;
}
for (;😉 {
printf(“服务器:接收请求\n”);
char buf[1024];
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof(addrcli);
ssize_t rb = recvfrom(sockfd, buf,
sizeof(buf), 0,
(struct sockaddr*)&addrcli,
&addrlen);
if (rb == -1) {
perror(“recvfrom”);
return -1;
}
printf(“服务器:向%s:%u发送响应\n”,
inet_ntoa(addrcli.sin_addr),
ntohs(addrcli.sin_port));
if (sendto(sockfd, buf, rb, 0,
(struct sockaddr*)&addrcli,
addrlen) == -1) {
perror(“sendto”);
return -1;
}
}
return 0;
}
代码:udpcli.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char* argv[]) {
if (argc < 3) {
fprintf(stderr, “用法:%s <IP地址> "
“<端口号>\n”, argv[0]);
return -1;
}
printf(“客户机:创建网络套接字\n”);
int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“客户机:准备地址\n”);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
for (;😉 {
printf(”> “);
char buf[1024];
fgets(buf, sizeof(buf) / sizeof(buf[0]),
stdin);
if (!strcmp(buf, “!\n”))
break;
printf(“客户机:向%s:%u发送请求\n”,
inet_ntoa(addr.sin_addr),
ntohs(addr.sin_port));
if (sendto(sockfd, buf, strlen(buf) *
sizeof(buf[0]), 0,
(struct sockaddr*)&addr,
sizeof(addr)) == -1) {
perror(“sendto”);
return -1;
}
printf(“客户机:接收响应\n”);
ssize_t rb = recv(sockfd, buf,
sizeof(buf) - sizeof(buf[0]), 0);
if (rb == -1) {
perror(“recv”);
return -1;
}
buf[rb / sizeof(buf[0])] = ‘\0’;
printf(”< %s", buf);
}
printf(“客户机:关闭套接字\n”);
if (close(sockfd) == -1) {
perror(“close”);
return -1;
}
printf(“客户机:完成\n”);
return 0;
}
针对UDP套接字的connect函数并不象TCP套接字的connect函数一样通过三路握手过程建立所谓的虚电路连接,而仅仅是将传递给该函数的对方地址结构缓存在套接字对象中。此后通过该套接字发送数据时,可以不适用sendto函数,而直接调用send函数,有关接收方的地址信息从套接字对象的地址缓存中提取即可。
代码:concli.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char* argv[]) {
if (argc < 3) {
fprintf(stderr, "用法:%s <IP地址> "
“<端口号>\n”, argv[0]);
return -1;
}
printf(“客户机:创建网络套接字\n”);
int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“客户机:准备地址并连接\n”);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
最后的话
最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!
资料预览
给大家整理的视频资料:
给大家整理的电子书资料:
如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
- {
fprintf(stderr, "用法:%s <IP地址> "
“<端口号>\n”, argv[0]);
return -1;
}
printf(“客户机:创建网络套接字\n”);
int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror(“socket”);
return -1;
}
printf(“客户机:准备地址并连接\n”);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
最后的话
最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!
资料预览
给大家整理的视频资料:
[外链图片转存中…(img-6N8xuOwK-1715771297642)]
给大家整理的电子书资料:
[外链图片转存中…(img-irfx1ewf-1715771297642)]
如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!