Linux系统编程、网络编程
- 前言
- 一、进程的退出
- 二、消息队列
- 三、信号
- 四、线程
- 五.网络编程
- 1.字节序
- 2.Socket服务器和客户端的开发步骤
- 3.Socket连接使用的API详解
- 【服务器部分】— — — — — — — — — — — — — — — —
- 3.1 int socket() —— 指定讲“汉语”(连接协议TCP/UDP)
- 3.2 int bind() —— 地址准备好(我ip地址(楼号)是。我端口号(房间号)是。。)
- 3.3 listen() —— 监听(等待大家的来访,等别人敲门)
- 3.4 accept() —— 接受连接(同意别人进来房间)
- 3.5 数据收发
- 3.6 数据收发常用的第二套API(就比上面第一套的多一个flags参数,flags主要用来控制是否有阻塞等等。其它前面三个参数是一模一样)
- 【客户端部分】— — — — — — — — — — — — — — — —
- 3.7 客户端创建套接字socket()
- 3.8 客户端的connect函数
- 4.代码——Socket服务器和客户端的开发代码
前言
提示:这里可以添加本文要记录的大概内容:
linux系统编程一些疑惑点,
linux网络编程实现
一、进程的退出
正常退出
①Main函数调用return
②进程调用exit(),标准c库
③进程调用_exit()或者_Exit(),属于系统调用
补充:
④进程最后一个线程返回
⑤最后一个线程调用pthread_exit
异常退出
1.主动调用API abort
2.当进程收到某些信号时,如ctrl+C
3.最后一个线程对取消(cancellation),请求做出响应
父进程等待子进程退出
为什么要等待子进程退出
父进程等待子进程退出,并收集子进程退出状态
子进程退出状态不被收集,变成僵尸进程
1.不关心子进程的退出状态:wait()
2.关心子进程的的退出状态:wait(&status) ,
子进程退出exit(2)括号里面的数字自己设,是一种标记。退出后如果父进程里有调用wait(&status)函数,wait会收集子进程exit(2)返回的标识2,存在status中。需要用WEXITSTATUS(status)来翻译出来。
二、消息队列
消息队列操作:
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
形象举例:
假设你有一个忙碌的家庭,父母和孩子们需要在家里留言通知彼此。父母有一个留言板,孩子们可以写上自己的消息并贴在留言板上。这里的留言板就相当于消息队列,孩子是发送者,父母是接收者。
msgget(key_t key, int flag):创建一个新的留言板,父母和孩子们需要确定留言板的ID号码以及权限,才能够使用这个留言板。key就是留言板的ID号码,flag就是设置留言板的权限。
msgsnd(int msqid, const void *ptr, size_t size, int flag):孩子们写好了消息,需要传递给留言板,msqid就是留言板的ID(哪一个留言板),ptr就是孩子们的消息内容,size就是消息的长度,flag就是发送消息的方式。
msgrcv(int msqid, void *ptr, size_t size, long type,int flag):父母需要去留言板上查看孩子们写的消息,msqid就是留言板的ID(哪一个留言板),ptr就是存放消息内容的地方,size就是接收消息的长度,type就是选择接收哪种类型的消息,flag就是接收消息的方式。
msgctl(int msqid, int cmd, struct msqid_ds *buf):父母需要对留言板进行一些操作,比如清空留言板或者删除留言板,msqid就是留言板的ID(哪一个留言板),cmd就是操作的指令,buf就是存放操作结果的地方。
疑惑点
疑惑点一
为什么下面这段代码里的msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);的第第三个参数不是sizeof(sendBuf),而只传了sendBuf.mtext的大小,那sendBuf结构体中的long mtype为什么不用传,为什么不是传整个结构体的长度?
//GPT回答:首先sizeof(sendBuf)包含了mtype和mtext的大小,但msgsnd()函数只需要消息内容(mtext)的大小,所以用strlen(sendBuf.mtext)。(消息类型)mtype不需要传入msgsnd()函数的长度参数中,因为mtype(数据类型)的作用只是:消息队列是根据mtype来进行消息分类和筛选的,msgsnd()函数在发送消息时会根据mtype进行筛选匹配。
//我简而言之:因为msgsnd函数中封装好了它做了处理,我们传&sendBuf地址给它,msgsnd函数内部会根据这个地址自动通过我们设置的类型 sendBuf.mtype 来进行消息分类和匹配,然后 msgsnd函数 内部再以 sendBuf.mtext 作为我们需要传送的数据为长度,并且我们也只需要发送这个数据。
msgSend.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
// int msgflg);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[256]; /* message data */
};
int main()
{
//1.huoqu
struct msgbuf sendBuf = {888,"this is message from quen"};
struct msgbuf readBuf;
memset(&readBuf,0,sizeof(struct msgbuf));
key_t key;
key = ftok(".",'m');
printf("key=%x\n",key);
int msgId = msgget(key, IPC_CREAT|0777);
if(msgId == -1 ){
printf("get que failuer\n");
}
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
//为什么这里传sendbuf的地址,但是第三个参数传数据长度的时候却只传sendBuf.mtext的长度,而不是整个结构体的长度?
//GPT回答:首先sizeof(sendBuf)包含了mtype和mtext的大小,但msgsnd()函数只需要消息内容的大小,所以用strlen(sendBuf.mtext)。mtype不需要传入msgsnd()函数的参数中,因为mtype(数据类型)的作用只是:消息队列是根据mtype来进行消息分类和筛选的,msgsnd()函数在发送消息时会根据mtype进行筛选匹配。
//我简而言之:因为msgsnd函数中封装好了它做了处理,我们传&sendBuf地址给它,msgsnd函数内部会根据sendBuf.mtype进行消息分类和匹配,然后msgsnd函数内部会以sendBuf.mtext为我们需要传送的数据为长度,我们也只需要发送这个数据
printf("send over\n");
msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),988,0);
printf("reaturn from get:%s\n",readBuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
三、信号
疑惑点
1.signal函数的原型:
按照下图的紫色字体,一步一步拆解:
四、线程
疑惑点
1.线程的创建
#include <pthread.h>
/**
* 创建一个新线程
*
* pthread_t *thread: 指向线程标识符的指针,线程创建成功时,用于存储新创建线程的线程标识符
* const pthread_attr_t *attr: pthead_attr_t结构体,这个参数可以用来设置线程的属性,如优先级、栈大小等。如果不需要定制线程属性,可以传入 NULL,此时线程将采用默认属性。
* void *(*start_routine)(void *): 一个指向函数的指针,它定义了新线程开始执行时的入口点。这个函数必须接受一个 void * 类型的参数,并返回 void * 类型的结果
* void *arg: start_routine 函数的参数,可以是一个指向任意类型数据的指针
* return: int 线程创建结果
* 成功 0
* 失败 非0
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
1.1 int pthread_create函数每个参数的含义
int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
①pthread_t *thread
1.每个线程都有一个唯一的标识符(即线程ID),这个标识符是通过pthread_t类型的变量来表示的,当pthread_create成功创建一个线程时,它会将新线程的标识符存储在thread参数指向的位置。
2.pthread_t 定义在头文件<pthreadtypes.h>中,实际上是long类型(long和long int是相同类型的不同写法)的别名。
typedef unsigned long int pthread_t;
②const pthread_attr_t *attr
pthead_attr_t 结构体,这个参数可以用来设置线程的属性,如优先级、栈大小等。 如果不需要定制线程属性,可以传入 NULL,此时线程将采用默认属性。
③void*(* start_routine) (void * )
1.这个参数中的每个*的含义。
【(* start_routine)函数指针】的【变量start_routine】是一个指向函数的指针,
该函数有一个【void类型的参数】 (void * )
该函数返回一个【void类型的指针】void*
通俗易懂的解释就是,这行代码定义了一个指针变量, 该指针变量指向一个【可以接受任意类型的参数】并返回【任意类型的指针】的函数。 即:它的参数和返回值都是void *指针。
2.其实函数名是一种指针,它指向函数的起始地址。
所以 void*(* start_routine) (void * )就相当于void* start_routine (void * )
当我们通过函数名调用函数时,程序实际上是通过这个地址找到并执行函数的代码。因此,可以说函数名是一种指针,它指向函数的起始地址。这种特性使得我们可以将函数名赋值给一个指针变量,即函数指针,通过这个函数指针来间接调用函数。
④void *arg
void * arg是 pthread_create函数的 参数 void*(* start_routine) (void * ) 的参数;
void * arg 对应 void*(* start_routine) (void * ) 的 (void * ),
void * arg 传入的值给 void*(* start_routine) (void * ) 的 (void * ),
void * arg可以是一个指向任意类型数据的指针。
1.2 int pthread_create函数的返回值return
- return: int 线程创建的返回值(结果)
-
成功 0
-
失败 非0
2.线程的退出
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
- 线程只是从启动例程中返回,返回值是线程的退出码。
- 线程可以被同一进程中的其他线程取消。
- 线程调用pthread_exit
2.1 pthread_exit
#include <pthread.h>
int pthread_exit(void *rval_ptr);
rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
3.互斥锁
3.1 死锁
前提条件:至少有两把锁
一开始,线程1手里拿着a锁,线程2手里拿着b锁;
然后呢,线程1又想去拿到b锁,线程2又想拿到a锁;
两个线程互相都想拿到对方手里的锁
,但是又拿不到,所以两个线程就一直卡拿锁那一步。导致死锁。
五.网络编程
1.字节序
Little endian 小端字节序 :把数据的低位先放在内存的中起始位置
Big endian 大端字节序 :把数据的高位先放在内存的中起始位置
一般网络传输时的字节序 =大端字节序
x86系列CPU都是 = 小端字节序.
1.1 字节序转换api
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue); //返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue); //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值
2.Socket服务器和客户端的开发步骤
如上图,TCP服务器和TCP客户端连接的过程如下:
①首先TCP服务器这边,先调用socket()来创建套接字。
套接字这个术语的由来可以追溯到英文单词"socket",意思是 “插座"或"接口” 。在计算机网络编程中,套接字就像是一种插座,它提供了通信的接口,使得不同计算机之间能够进行数据传输和通信。
②调用bind() 给套接字绑定IP地址和端口号
③调用listen() 监听网络连接,等别人来连
————————————
④TCP客户端这边调用socket()来创建套接字
⑤如图红色线,因为TCP客户端知道TCP服务器的IP地址和端口号,所以TCP客户端直接调用connect() 去连接TCP服务器
————————————
⑥TCP服务器监听到有客户端申请接入,调用accept() 接受这个客户端的连接
————————————
⑦TCP客户端和TCP服务端,进行数据的交互
⑧关闭套接字,断开连接
3.Socket连接使用的API详解
【服务器部分】— — — — — — — — — — — — — — — —
3.1 int socket() —— 指定讲“汉语”(连接协议TCP/UDP)
int socket (int domain, int type, int protocol);3.2 int bind() —— 地址准备好(我ip地址(楼号)是。我端口号(房间号)是。。)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);3.2.1 端口号需要用到 字节序转换API(看1.1)
因为这里的端口号会传到网络上去,
我们现在主机是x86系列CPU,都是小端字节序
而网络传输用的是大端字节序
所以需要使用字节序转换API来转换一下。
3.2.2 地址转换API
int inet_aton(const char* straddr,struct in_addr *addrp);把字符串形式的"192.168.1.123"转为网络传输能识别的格式
例:int inet_aton(“192.168.1.123”, &s_addr.sin_addr);
此时变量s_addr.sin_addr的值就是网络能识别的格式。
把网络格式的ip地址转为字符串形式(我们能看得懂的形式)
3.3 listen() —— 监听(等待大家的来访,等别人敲门)
int listen(int sockfd, int backlog);3.4 accept() —— 接受连接(同意别人进来房间)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
出错的时候返回值是-1,
成功的时候返回值是新的套接字描述符
3.5 数据收发
ssize_t write(int fd, const void*buf, size_t nbytes); ssize_t read(int fd, void *buf, size_t nbyte);3.6 数据收发常用的第二套API(就比上面第一套的多一个flags参数,flags主要用来控制是否有阻塞等等。其它前面三个参数是一模一样)
ssize_t send(int s, const void *msg, size_t len, int flags); ssize_t recv(int s, void *buf, size_t len, int flags);【客户端部分】— — — — — — — — — — — — — — — —
3.7 客户端创建套接字socket()
3.8 客户端的connect函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);4.代码——Socket服务器和客户端的开发代码
4.1 Socket服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
//chenlichen
int main(int argc, char **argv)
{
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
int mark = 0;
char msg[128] = {0};
// char *msg = "I get your connect";
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3){
printf("param is not good\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1. socket
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
//2. bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3. listen
listen(s_fd,10);
//4. accept
int clen = sizeof(struct sockaddr_in);
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
mark++;
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0){
if(fork()==0){
while(1){
sprintf(msg,"welcom No.%d client",mark);
write(c_fd,msg,strlen(msg));
sleep(3);
}
}
//5. read
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd, readBuf, 128);
if(n_read == -1){
perror("read");
}else if(n_read>0){
printf("\nget: %d\n",n_read);
}else{
printf("client quit\n");
break;
}
}
break;
}
}
return 0;
}
4.2 Socket客户端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
int c_fd;
int n_read;
char readBuf[128];
int tmp;
// char *msg = "msg from client";
char msg[128] = {0};
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){
printf("param is not good\n");
exit(-1);
}
printf("%d\n",getpid());
//1. socket
c_fd = socket(AF_INET, SOCK_STREAM, 0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
//2.connect
if(connect(c_fd, (struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
while(1){
if(fork()==0){
while(1){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd, readBuf, 128);
if(n_read == -1){
perror("read");
}else{
printf("\nget:%s\n",readBuf);
}
}
}
//3.send
//4.read
return 0;
}
标签:pthread,addr,int,void,编程,网络,线程,Linux,include
From: https://blog.csdn.net/Thenunaoer/article/details/140064845