网络编程(一)TCP和socket

网络编程(一)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
image-20210801184925565
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客户端代码:
image-20210801194745911
建立连接,使用连接,结束连接
//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服务器代码
image-20210801194745911

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);


        }
    }


}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值