网络字节序:
大端和小端的概念:
大端:地位地址存放高位数据,高位地址存放地位数据
小端:地位地址存放地位数据,高位地址存放高位数据
在计算机中数据的存放方式都是以小端的形式,在网络中却是以大端的形式存放,所以在linux中有几个函数是进行存储方式的转换的
函数包含的头文件:<arpa/inet.h>
htonl函数:uint32_t htonl(uint32_t hostlong);
作用:把本地的(小端)IP地址转换为网络中(大端)需要的IP地址形式
htons函数:uint16_t htons(uint16_t hostshort);
作用:把本地的(小端)port端口号转换为网络中(大端)需要的port端口号形式
ntohl函数:uint32_t ntohl(uint32_t netlong);
作用:把网络中的(大端)IP地址转换为本地的(小端)IP地址形式
ntohs函数:uint16_t ntohs(uint16_t netshort);
作用:把网络中的(大端)port端口号转换为本地的(小端)port端口号形式
查看监听状态和连接状态的命令
netstat命令:netstat -anp | grep 8888
a表示显示所有
n表示显示的时候以数字的方式来显示
p表示显示进程的消息
简单的c/s模型
简单的c/s模型
1.函数使用:
1.socket函数:包含在头文件 <sys/types.h>或者<sys/socket.h>
➡函数原型:int socket(int domain, int type, int protocol);
作用:创建一个用于链接的套接字
2.connect函数:包含在头文件 <sys/types.h>或者<sys/socket.h>
➡函数原型:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
作用:连接服务器的函数
3.memset函数: 包含在头文件 <string.h>
➡函数原型:void *memset(void *s, int c, size_t n);
作用:把数组中的内容置为某一个值
4.inet_pton和inet_ntop函数: 包含在头文件 <arpa/inet.h>
➡函数原型:int inet_pton(int af, const char *src, void *dst);
参数 af:指定的IP协议类型,有AF_INET和AF_INET6两个选项
参数 src:传入的IP地址(点分十进制)
参数 dst:传出的地址,转换后的网络字节序IP地址
返回值:成功返回1,出现异常返回0(也就是指向了一个无效的IP地址),失败返回-1
作用:给sockaddr_in结构体中的s_addr的值附上地址值,也是把点分十进制的字节序转换为网络的字节序
➡函数原型:const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
参数 af:指定的IP协议类型,有AF_INET和AF_INET6两个选项
参数 src:传入的IP地址,网络字节序IP地址
参数 dst:地址转换后存放的缓冲区,本地字节序
参数 size:缓冲区大小
返回值:成功返回dst,失败返回NULL
作用:把网络字节序转换为点分十进制字节序
5.bind函数:包含在头文件 <sys/types.h>或者<sys/socket.h>
➡函数原型:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
作用:绑定IP地址
6.listen函数:包含在头文件 <sys/types.h>或者<sys/socket.h>
➡函数原型:int listen(int sockfd, int backlog);
作用:监听客户端链接请求
7.accept函数:包含在头文件 <sys/types.h>或者<sys/socket.h>
➡函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
作用:接受客户端的连接
服务器代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>//使用网络包含的库
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd < 0)
{
perror("soket error");
return -1;
}
struct sockaddr_in serv;
bzero(&serv, sizeof(serv));//该函数初始化serv
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);//表示使用本地任意可用IP地址
int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
if(ret < 0)
{
perror("bind error");
return -1;
}
//监听
listen(lfd, 128);
//接受链接
int cfd = accept(lfd, NULL, NULL);
printf("lfd==[%d], cfd==[%d]\n", lfd, cfd);
int n = 0;
int i = 0;
char buf[1024];
while(1)
{
//读数据
memset(buf, 0x00, sizeof(buf));
n = read(cfd, buf, sizeof(buf));
if(n <= 0)
{
printf("read error or client close\n");
break;
}
printf("n==[%d], buf==[%s]\n", n, buf);
for(i = 0;i < n;i++)
{
//buf[i] = toupper(buf[i]);//该函数把小写字母转换成大写字母
}
//发生数据
write(cfd, buf, n);
}
//关闭监听文件描述符和通信文件描述符
close(lfd);
close(cfd);
return 0;
}
客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
int main()
{
//创建socket,与服务端进行通信
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd < 0)
{
perror("socket error");
return -1;
}
//连接服务器
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);//赋值地址
printf("[%x]\n", serv.sin_addr.s_addr);//输出地址
int ret = connect(cfd, (struct sockaddr*)&serv, sizeof(serv));
if(ret < 0)
{
perror("connect error");
return -1;
}
int n = 0;
char buf[256];
while(1)
{
//读标准输入数据
memset(buf, 0x00, sizeof(buf));
n = read(STDIN_FILENO, buf, sizeof(buf));
//发送数据
write(cfd, buf, n);
//读服务端发来的数据
memset(buf, 0x00, sizeof(buf));
n = read(cfd, buf, sizeof(buf));
if(n <= 0)
{
printf("read error or server closed\n");
break;
}
printf("n==[%d], buf==[%s]\n", n, buf);
}
//关闭套接字
close(cfd);
return 0;
}
多进程并发的c/s模型
1.使用到的函数
fork函数:包含在头文件 <sys/types.h>和<unistd.h>
➡函数原型:pid_t fork(void);
作用:创建一个子进程
返回值:fork函数存在两个返回值
在父进程中,fork会返回新创建的子进程ID
在子进程中,fork会返回0
注意:在调用fork函数后,fork函数后面的所有代码会执行两遍
两次的执行流程:
如果fork执行成功,那么会先返回一个大于0的数,也就是子进程的ID,会执行父进程代码,当执行完fork函数后面的代码后,程序会再一次执行fork函数后面的代码,这时候fork返回值为0
struct sigaction结构体:包含在头文件<signal.h>
结构体原型:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
成员sa_handler:这是一个函数指针,指定一个信号处理函数
成员sa_mask:用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
成员sa_falgs:用来设置信号处理的其他相关操作,下列的数值可用
可选选项:
1.SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
2.SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
3.SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
sigaction函数:包含在头文件<signal.h>
➡函数原型:int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
作用:检查或修改与指定信号相关联的处理动作
signum参数指出要捕获的信号类型
act参数指定新的信号处理方式,
oldact参数输出先前信号的处理方式
服务端代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<signal.h>
#include<pthread.h>
#include "wrap.h"
#define SRV_PORT 10110
void catch_child(int signum)
{
while((waitpid(0, NULL, WNOHANG))>0);//阻塞子进程
return;
}
int main(int argc, int *argv[])
{
int lfd;
struct sockaddr_in srv_addr;
//memset(&srv_addr, 0, sizeof(srv_addr));
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
Listen(lfd, 128);
struct sockaddr_in clt_addr;//客户端使用的
socklen_t clt_addr_len = sizeof(clt_addr);//accept参数需要使用的类型
pid_t pid;
int cfd;
int retf;
while(1)
{
cfd = Accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
//创建子进程
pid = fork();
if(pid < 0)//子进程创建失败
{
perr_exit("fork error");
}
else if(pid == 0){//子进程创建成功
break;
}
else//处理主进程
{
//回收子进程
struct sigaction act;//创建一个结构体
act.sa_handler = catch_child;//捕捉子进程
sigemptyset(&act.sa_mask);//清空子进程
act.sa_flags = 0;
retf = sigaction(SIGCHLD, &act, NULL);
if(retf != 0)
{
perr_exit("sigaction error");
}
close(cfd);
continue;
}
}
char buf[1024] = {0};
int ret = 0;
int i = 0;
if(pid == 0)
{
while(1)
{
ret = Read(cfd,buf, sizeof(buf));
if(ret == 0)
{
close(cfd);
exit(0);
}
for(i = 0;i < ret; i++);
{
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret);
}
}
return 0;
}
3.多线程并发c/s模型
1.使用的函数
1.pthread_create函数:包含在头文件<pthread.h>
➡函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
作用:创建一个子线程
参数1:事先创建好的pthread_t类型的参数。成功时tidp指向的内存单元被设置为新创建线程的线程ID
参数2:用于定制各种不同的线程属性
参数3:新创建线程从此函数开始运行
参数4:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传
返回值:若线程创建成功返回0,否则返回出错编号
2.pthread_detach函数:包含在头文件<pthread.h>
➡函数原型:int pthread_detach(pthread_t thread);
作用: 线程分离状态,线程主动与主控线程断开关系。使用pthread_exit或者线程自动结束后,其退出状态不由其他线程获取,而直接自己自动释放
参数:传入指定的线程编号
返回值:成功返回0,失败返回错误编号
服务器代码
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
struct s_info { //定义一个结构体, 将地址结构跟cfd捆绑
struct sockaddr_in cliaddr;
int connfd;
};
void *do_work(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 可用"[+d"查看
while (1) {
n = Read(ts->connfd, buf, MAXLINE); //读客户端
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break; //跳出循环,关闭cfd
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port)); //打印客户端信息(IP/PORT)
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]); //小写-->大写
Write(STDOUT_FILENO, buf, n); //写出至屏幕
Write(ts->connfd, buf, n); //回写给客户端
}
Close(ts->connfd);
return (void *)0;//结束该子线程
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
struct s_info ts[256]; //根据最大线程数创建结构体数组.
int i = 0;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建一个socket, 得到lfd
bzero(&servaddr, sizeof(servaddr)); //地址结构清零
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP
servaddr.sin_port = htons(SERV_PORT); //指定端口号 8000
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定
Listen(listenfd, 128); //设置同一时刻链接服务器上限数
printf("Accepting client connect ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞监听客户端链接请求
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
/* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
pthread_detach(tid); //线程已经创建好,需要关闭线程句柄 //子线程分离,防止僵线程产生.
i++;
}
return 0;
}
1.setsockopt函数:包含在头文件<sys/types.h>和<sys/socket.h>
函数原型:
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
作用:用于任意类型、任意状态套接口的设置选项值
参数含义:
sockfd:标识一个套接口的描述字;
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6; optname:需设置的选项;
optval:指针,指向存放选项待设置的新值的缓冲区;
optlen:optval缓冲区长度;
返回值:若成功返回0,发生错误会返回-1
使用实例
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));
2.getsockopt函数:包含在头文件<sys/types.h>和<sys/socket.h>
函数原型:
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
作用:用于获取任意类型、任意状态套接口的选项当前值,并将结果存入optval
参数含义:
sockfd:标识一个套接口的描述字;
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP;
optname:需获取的套接口选项;
optval:指针,指向存放所获得选项值的缓冲区;
optlen:指针,指向optval缓冲区的长度值;
返回值:若成功返回0,发生错误会返回-1
4.半关闭
意思:TCP链接中A发送FIN请求关闭,B端回应ACK后,A端进入FIN_WAIT_2状态,进入半关闭状态(实际上就是只有一端关闭)
close函数和shutdown函数的区别:
close函数只能关闭一个套接字或者是一个文件描述符
shutdown函数可以仅关闭读,仅关闭写,关闭读写,情况更加精细。同时,当出现多个文件描述符指向同一socket时,调用shutdown,可以将所有的指向都断开
注意:
如果有多个进程共享一个套接字,close 每被调用一次,计数减 1,直到计数为0时,也就是所用进程都调用了 close,套接字将被释放。
在多进程中如果一个进程调用了 shutdown(sfd,SHUT RDWR)后,其它的进程将无法进行通信。但,如果一2个进程 close(sfd)将不会影响到其它进程
shutdown函数原型:包含在头文件<sys/socket.h>
int shutdown(int sockfd, int how);
参数sockfd:指定的套接字
参数how:指定的断开方式,有一下选项
SHUT_RD:关闭sockfd上的读功能,此选项将不允许sockfd进行读操作,该套接字不再接受数据,任何在当前套接字缓冲区的数据都会被丢弃
SHUT_WR:关闭sockfd上的写功能,此选项将不允许sockfd进行写操作,进程不能对此套接字进行写操作
SHUT_RDWR:关闭sockfd的读写功能,相当于调用两次shutdown,首先是SHUT_RD,再SHUT_WR