1,gethostname() : 返回本地主机的标准主机名。
原型如下:
#include <unistd.h>
int gethostname(char *name, size_t len);
参数说明:
接收缓冲区name,其长度必须为len字节或是更长,存获得的主机名。
接收缓冲区name的最大长度
2,gethostbyname():用域名或主机名获取IP地址
包含头文件
#include <netdb.h>
#include <sys/socket.h>
函数原型
struct hostent *gethostbyname(const char *name);
传入值是域名或者主机名,例如"www.google.cn"等等。传出值,是一个hostent的结构。
返回hostent结构体类型指针,调用失败,返回NULL,hostent->h_addr_lisst
表示的是主机的ip地址,注意,这个是以网络字节序存储的。千万不要直接用printf带%s参数来打这个东西,真正需要打印出这个IP的话,需要调用inet_ntop()。
- 注意点:
struct in_addr 结构体:表示一个32位的IPv4地址。
in_addr_t一般为32位的unsigned int,其字节顺序为网络字节序,即该无符号数采用大端字节序。其中每8位表示一个IP地址中的一个数值。
打印的时候可以调用inet_ntoa()函数将其转换为char*类型。
头文件为:#include <arpa/inet.h>
获取本地主机,通过本地主机名获取本地ip
3,设置I/O为非阻塞模式代码小抄==
14 void activate_nonblock(int fd);
15 {
16 int ret;
17 int flags = fcntl(fd, F_GETFL);
18 if(flags == -1)
19 EXX_EXIT("fcntl error");
20 flags |= O_NONBLOCK;
21 ret = fcntl(fd, F_SETFL, flags);
22 if(ret == -1)
23 ERR_EXIT("fcntl error");
24 }
25
3.2设置为阻塞模式
void deactivate_nonblock(int fd)
27 {
28 int ret;
29 int flags = fcntl(fd, F_GETFL);
30 if(flags == -1)
31 ERR_EXIT("fcntl");
32 flags &= ~O_NONBLOCK;
33 ret = fcntl(fd, F_SETFL, flags);
34 if(ret == -1)
35 ERR_EXIT("fcntl");
36
37 }
4,fd_set是一组文件描述符字的集合
-
FD_ZERO(fd_set *fdset);//将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
-
FD_SET(fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。
-
FD_CLR(fd_set *fdset);用于在文件描述符集合中删除一个文件描述符。
-
FD_ISSET(int fd,fd_set *fdset);用于测试指定的文件描述符是否在该集合中。
原文链接:https://blog.csdn.net/sin0803/article/details/36192779
5,getsockopt()
函数用于获取任意类型、任意状态套接口的选项当前值
int getsockopt(int socket, int level, int option_name,
void *restrict option_value, socklen_t *restrict option_len);
功能:获取一个套接字的选项
参数:
socket:文件描述符
level:协议层次
SOL_SOCKET 套接字层次
IPPROTO_IP ip层次
IPPROTO_TCP TCP层次
option_name: 选项的名称(套接字层次)
SO_BROADCAST 是否允许发送广播信息
SO_REUSEADDR 是否允许重复使用本地地址
SO_SNDBUF 获取发送缓冲区长度
SO_RCVBUF 获取接收缓冲区长度
SO_RCVTIMEO 获取接收超时时间
SO_SNDTIMEO 获取发送超时时间
option_value: 获取到的选项的值
option_len: value的长度
返回值:
成功:0
失败:-1
6,⭐⭐发送文件描述符
void send_fd(int sock_fd, int fd)
{
int ret;
struct msghdr msg;
struct cmsghdr *p_cmsg;
struct iovec vec;
char cmsgbuf[CMSG_SPACE(sizeof(fd))];
int *p_fds;
char sendchar = 0;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
p_cmsg = CMSG_FIRSTHDR(&msg);
p_cmsg->cmsg_level = SOL_SOCKET;
p_cmsg->cmsg_type = SCM_RIGHTS;
p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
p_fds = (int*)CMSG_DATA(p_cmsg);
*p_fds = fd;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
vec.iov_base = &sendchar;
vec.iov_len = sizeof(sendchar);
ret = sendmsg(sock_fd, &msg, 0);
if (ret != 1)
ERR_EXIT("sendmsg");
}
struct msghdr结构体,用于接收来自应用层的数据包
#include<sys/socket.h>
struct msghdr {
void * msg_name ; / * 消息的协议地址 * / 协议地址和套接口信息,在非连接的UDP中,发送者要指定对方地址端口,接受方用于的到数据来源,如果不需要的话可以设置为NULL(在TCP或者连接的UDP中,一般设置为NULL)
socklen_t msg_namelen ; / * 地址的长度 * /
struct iovec * msg_iov ; / * 多io缓冲区的地址 * /
int msg_iovlen ; / * 缓冲区的个数 * /
void * msg_control ; / * 辅助数据的地址 * /
socklen_t msg_controllen ; / * 辅助数据的长度 * /
int msg_flags ; / * 接收消息的标识 * /
} ;
7,⭐⭐接收文件描述符
int recv_fd(const int sock_fd)
{
int ret;
struct msghdr msg;
char recvchar;
struct iovec vec;
int recv_fd;
char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
struct cmsghdr *p_cmsg;
int *p_fd;
vec.iov_base = &recvchar;
vec.iov_len = sizeof(recvchar);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
msg.msg_flags = 0;
p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg));
*p_fd = -1;
ret = recvmsg(sock_fd, &msg, 0);
if (ret != 1)
ERR_EXIT("recvmsg");
p_cmsg = CMSG_FIRSTHDR(&msg);
if (p_cmsg == NULL)
ERR_EXIT("no passed fd");
p_fd = (int*)CMSG_DATA(p_cmsg);
recv_fd = *p_fd;
if (recv_fd == -1)
ERR_EXIT("no passed fd");
return recv_fd;
}
8,begin_session,socketpair进行父子进程间通信
void begin_session(session_t *sess)
{
activate_oobinline(sess->ctrl_fd);
/*
int sockfds[2]; //内部进程间通道的建立
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds) < 0) //创建了一对无名的套接字描述符 和管道类似
ERR_EXIT("socketpair");
*/
priv_sock_init(sess);
pid_t pid;
pid = fork();
if (pid < 0)
ERR_EXIT("fork");
if (pid == 0)//子进程
{
// ftp服务进程 //与外界进行交互通讯,接受客户端发送过来的命令请求
/*
close(sockfds[0]);
sess->child_fd = sockfds[1];
*/
priv_sock_set_child_context(sess);
handle_child(sess);
//设置子进程通讯套接字
}
else
{
// nobody进程 将父进程改为nobody进程, 父进程接收的是子进程发送接受的 仅仅是起到一些辅助的作用
//内部子进程,只是起辅助作用不与外部进行通讯
/*close(sockfds[1]);
sess->parent_fd = sockfds[0];*/
priv_sock_set_parent_context(sess);
handle_parent(sess);
}
}
将(父)进程转换成nobody进程
发生段错误
进入gdb环境,r运行一下,bt追踪端错误
指针指向一个字符串,字符串时常量,常量是不能修改的,容易出现段错误,不如就用数组
extern关键字
百科名片:extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。另外,extern也可用来进行链接指定。
extern的原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”
各个文件中定义的全局变量名不可相同。
在链接阶段,各个文件的内容(实际是编译产生的obj文件)是被合并到一起的,因而,定义于某文件内的全局变量,在链接完成后,它的可见范围被扩大到了整个程序。
extern int a;
仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
引用一个定义在其它模块的全局变量或函数(如,全局函数或变量定义在A模块,B欲引用)有两种方法,一、B模块中include模块A的头文件。二、模块B中对欲引用的模块A的变量或函数重新声明一遍,并前加extern关键字。 与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,
忘了就再看这个:https://www.cnblogs.com/Azhu/articles/2454483.html
fclose()和close()
fclose()是与文件流相关的函数.在fopen()的帮助下打开文件并将流分配给FILE * ptr.然后,您将使用fclose()关闭打开的文件.
close()是与文件描述符相关的函数.在open()的帮助下打开文件并将描述符赋值给int fd.然后,您将使用close()关闭打开的文件.
fopen(),fclose()等函数是C标准函数,而open(),close()等其他类别是POSIX特有的.这意味着用open(),close()等编写的代码不是标准的C代码,因此是不可移植的.用fopen(),fclose等编写的代码是标准代码,可以移植到任何类型的系统上.
为什么使用多进程而不是多线程?
原因是在多线程或IO复用的情况下,当前目录是共享的,无法根据每一个连接来拥有自己的当前目录,也就是说当前用户目录的切换会影响到其他的用户。
出现的问题
Segmentation fault (core dumped)
eyedebug是不可能的,
方法1:
用dmesg和addr2line查错
dmesg | grep miniftpd
addr2line -e miniftpd core地址
方法2
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200528164338454.png)
也可以得到
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200528163302193.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mjk5NTk2,size_16,color_FFFFFF,t_70)
感谢https://blog.csdn.net/stpeace/article/details/49806473,gdb调试要在多看看这文章(●’◡’●)
写代码时一定要多多注意指针,地址,定义的时候传值的时候都要过脑子考虑呀,改bug真是太费劲了
判断密码是否正确的sp_pwdp
其中passwd 是用户输入的密码,sp->sp_pwdp是从shaodw文件中取得的密文,
通过crypt函数把用户输入的明文重新加工为密文,然后比较用户原来的密文,和加工出来的
密文是否相同,判断登录是否成功.
char *newpwd;
struct spwd *sp;
sp = getspnam(name);
if (NULL == sp) return 1;
newpwd = crypt(passwd,sp->sp_pwdp);
return ( strcmp ( newpwd, sp->sp_pwdp ) == 0 )?0:1;
send_fd( , ), recv_fd(),传送和发送文件描述符
传送进程描述符,简单的来说,就是进程A打开一个文件f,获得了一个文件描述符fd1,然后进程A将该描述符通过某些方式,传递给了B,此时B就具有了描述符fd2(注意,fd1 不一定等于fd2),从而可以通过fd2对文件f进行读写等一系列的操作。其实本质上
相当于A,B两个进程同时打开了文件f。
具体实现其实比较简单,例如当一个父进程要向子进程传递一个文件描述符时,首先会在fork产生子进程以前,调用socketpair建立一个套接字对,用于父子进程之间的通信。之后父进程打开文件f,获得文件描述符fd1。接着通过sendmsg将包含文件描述符fd1的消息发送出去,而在子进程通过recvmsg接收消息,从中获取出文件描述符fd2。最后,子进程就能通过操作fd2对文件f进行一系列的读写操作。
最后,需要注意的是,当发送进程将文件描述符传送给接收进程以后,通常会关闭该描述符。不过,发送进程关闭该描述符并不会真的关闭该文件或设备,其原因是该文件描述符仍然视为由接收进程打开(即使接收进程尚未接收到该描述符,此时称该描述符在飞行中…in flight)
在写函数的时候,尤其是读写函数的时候,什么时候传递地址,什么时候直接传递,要分的清
会有什么问题
是否有资源泄露的问题,如有无内存泄露,是否存在句柄打开而为关闭的情况
尽可能详细说明每个函数的时间复杂度和空间复杂度,