网络编程(一)TCP和socket
1.网络字节序:
大小端:
小端:低位低内存地址、高位存高内存地址
大端:低位存高内存地址、高位存低内存地址
0x0102 小端 L ---------H
02 01
大端 H---------L
01 02
规定网络上走的数据都是大端的:
主机字节序系统能够识别,通信过程是 主机字节序 —》大端(网络字节序)—》主机字节序
协议里面大于两个字节才需要转换网络字节序
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t noths(uint16_t netshort);
2.点分十进制串转换
man inet_pton
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
af:
AF_INET IPV4
AF_INET6 IPV6
src:点分十进制的字符串的首地址
dst:网络字节序的首地址
man inet_ntop
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
af:
AF_INET IPV4
AF_INET6 IPV6
src网络字节序的首地址
dst点分十进制的字符串的首地址
size 存储点分十进制串的数组大小 255.255.255.255 16个字节即可
3.Ipv4套接字结构体:
man 7 ip;
协议,IP,端口
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
仅了解IPV6套接字结构体
通用套接字结构体:
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
4.TCP客户端代码:
建立连接,使用连接,结束连接
//1.创建套接字:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: AF_INET
type: SOCK_STREAM 流式套接字用于tcp通信
protocol:0
成功返回文件描述符,失败为-1
//2.连接服务器:
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
sockfd:套接字
addr:ipv4套接字结构体的地址
addrlen:套接字结构体的长度
TCP客户端代码简单版本:
#include <unistd.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
//创建套接字
int sock_fd;
sock_fd = socket(AF_INET,SOCK_STREAM,0);
//连接服务器
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET,"192.168.21.29",&addr.sin_addr.s_addr);
connect(sock_fd,(struct sockaddr *)&addr,sizeof(addr));
//读写数据
char buf[1024]="";
while(1)
{
int n = read(STDIN_FILENO,buf,sizeof(buf));
write(sock_fd,buf,n);//发送数据给服务器
n = read(sock_fd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,n);
printf("\n");
}1
//关闭
close(sock_fd);
return 0;
}
5.TCP服务器代码
1.创建套接字
2.给套接字绑定固定的端口和ip地址(一台主机可能有多个Ip)
3.监听:
1)将套接字由主动变为被动
2)创建一个连接队列 分为已完成连接队列和未完成连接队列
4.提取连接accept:(服务多个客户端所以必须要这一步)
从已完成连接队列提取连接,提取连接得到的是已连接套接字,用这个已连接套接字和客户端通信
5.读写
6.关闭
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd:套接字
addr:ipv4套接字结构体地址
addrlen:ipv4套接字结构体的大小
返回值 成功0 失败1
int listen(int sockfd, int backlog);
sockfd:套接字
backlog:已完成连接队列和未完成连接队列之和的最大值 128
int accept(int socket,struct sockaddr *restrict address,socklen_t *restrict address_len);
从已完成的连接队列中提取新的连接,如果连接队列没有新的连接,则accept会阻塞。
socket:套接字
address:获取的客户端的ip和端口信息,IPV4套接字结构体地址
address_len:IPV4套接字结构体的大小的地址
socklen_t len = sizeof(struct sockaddr);
&len
返回值:新的已连接的套接字的文件描述符
步骤:
1.创建套接字 2.绑定 3.监听 4.提取 5.读写 6.关闭
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//创建套接字
int lfd = socket(AF_INET,SOCK_STREAM,0);
//绑定
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
// addr.sin_addr.s_addr = INADDR_ANY;//绑定的是通配地址
inet_pton(AF_INET,"192.168.21.37",&addr.sin_addr.s_addr);
int ret = bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if(ret < 0)
{
perror("");
exit(0);
}
//监听
listen(lfd,128);
//提取
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int cfd = accept(lfd,(struct sockaddr *)&cliaddr,&len);
char ip[16]="";
printf("new client ip=%s port=%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,
ip,16), ntohs(cliaddr.sin_port));
//读写
char buf[1024]="";
while(1)
{
bzero(buf,sizeof(buf));
// int n = read(STDIN_FILENO,buf,sizeof(buf));
// write(cfd,buf,n);
int n =0;
n = read(cfd,buf,sizeof(buf));
if(n ==0 )//如果read返回等于0,代表对方关闭
{
printf("client close\n");
break;
}
printf("%s\n",buf);
}
//关闭
close(lfd);
close(cfd);
return 0;
}
6.包裹函数
wrap.c 包裹了套接字函数,判错
7.黏包+包裹函数
服务器往客户端发了512B,又发了1024B,客户端收到连续的包,黏在一起了。
解决方法:
1.约定好,一次发送固定的字节数
2.数据的结尾加一个标记\n
3.头部加上数据的大小
读取固定的字节数数据
ssize_t Readn(int fd, void *vptr, size_t n){
size_t nleft; //unsigned int 剩余未读取的字节数
ssize_t nread; //int 实际读到的字节数
char *ptr;
ptr = vptr;
nleft = n;
while(nleft>0){
if(nread = read(fd,ptr,nleft))<0){
if(error == EINTR)
nread = 0;
else
return -1;
}else if(nread ==0 )
break;
nleft -= nread;
ptr += nread;
}
return n-nleft;
}
写固定数据
ssize_t Writen(int fd,const void *vptr,size_t n){
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while(nleft>0){
if((nwritten = write(fd,ptr,nleft))<=0){
if(nwritten<0 && errno==EINTR)
nwritten=0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
8.三次握手,四次挥手(重要)
三次握手是tcp建立连接的过程
9.多进程实现并发服务器
流程:
1、创建套接字
2、绑定
3、监听
4、while(1){
提取连接
fork创建子进程
子进程中,关闭fd,服务客户端
父进程关闭cfd,回收子进程的资源(通过信号回收)。
}
5、关闭
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include "wrap.h"
void free_process(int sig){
pid_t pid;
while(1){
pid = waitpid(-1,NULL,WNOHANG);
if(pid <= 0)//小于0 没有要等待的子进程,子进程全部退出 =0 子进程还没有退出;
{
break;
}else{
printf("child pid = %d\n",pid);
}
}
}
int main(int argc,int *argv[]){
//创建套接字,绑定
//阻塞住:
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGCHLD);
sigprocmask(SIG_BLOCK,&set,null);
int lfd = tcp4bind(8000,NULL);
//监听
Listen(lfd,128);
//提取、回收
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
while(1){
char ip[16]="";
//提取连接
int cfd = accept(lfd,(struct sockaddr *)&cliaddr,&len);
printf("new client ip = %s port = %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));
//fork创建子进程
pid_t pid;
pid = fork();
if(pid<0){
perror("");
exit(0);
}else if(pid == 0) //子进程{
//关闭Lfd
close(lfd);
char buf[1024]="";
int n = read(cfd,buf,sizeof(buf));
if(n<0){
perror("");
close(cfd);
break;
}else if(n==0){ //对方关闭
printf("client close:\n");
close(cfd);
exit(0); //断开子进程
}else{
printf("%s\n",buf);
write(cfd,buf,n);
// exit(0);
}
}else//父进程
{
close(cfd);
//回收
//注册信号回调
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = free_process;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD,&act,NULL); //注册之前,子进程退出怎么办?
sigprocmask(SIG_UNBLOCK,&set,NULL);
}
}
}