LinuxTCP编程详解

目录

一、创建套接字

二、绑定套接字

示例

三、监听套接字

四、等待套接字

五、服务器端示例

六、连接套接字

七、客户端示例

八、Send和Recv


C/S模式:Client客户端、Server服务器

TCP编程基于socket套接字实现,因此也习惯称为Socket编程

一、创建套接字

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

//创建套接字
int socket(int doman, int type, int protocol)
    
params: domain:  AF_INET----IPV4
				 AF_INET6---IPV6
    			 AF_UNIX,AF_LOCAL--本地通信  
                 此外还有AF_NETLINK用于做设备驱动/内核用户间通信、AF_PACKET原始套接字
    	
        type:    SOCK_STREAM 流式套接字 唯一对应TCP
    			 SOCK_DGRAM  数据报套接字 唯一对应UDP
    			 SOCK_RAW    原始套接字 
    	protocol:一般填0
            
return:成功时返回fd,出错时返回-1

二、绑定套接字

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

//绑定套接字----服务器使用
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    
params:	addr:    //配置完sockaddr_in(见下)后---强转为sockaddr指针
    	addrlen: //

return:成功返回0,出错返回-1
            
//补充
struct sockaddr{ //通用结构体 仅作为一种模型,实际上要将下面的结构体配置完成后强转为此结构体
    sa_family_t sa_family;  //2B 协议族
    char        sa_data[14];//IP+端口号
}
struct sockaddr_in{	//基于Internet通信的结构体
    sa_family_t    sin_family;//2B AF_INET(IPV4)
    in_port_t      sin_port;  //2B 端口号
    struct in_addr sin_addr;  //4B IP地址
    unsigned char sin_zero[8]; //无实际意义 只是为了跟sockaddr大小对齐
}
struct in_addr{	//Internet地址---32位网络字节序
    uint32_t s_addr;
}   

IP地址转换补充:

cpp 复制代码
#include <arpa/inet.h>

//将点分十进制形式IP地址的字符串 转换为32位网络字节序整数
//仅用于IPV4,出错时返回-1,因此不能转换全网广播255.255.255.255的地址
in_addr_t inet_addr(const char *cp) 


#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//将点分十进制形式IP地址的字符串 转换为32位网络字节序整数
//IPV4和IPV6都可用,能正确处理全网广播的地址
int inet_pton(int af, const char *src, void *dst)
    
params: af----AF_INET或AF_INET6
    	src---源地址---点分十进制形式IP字符串
    	dst---转换结果地址---32位整数
    
return: 成功返回1 出错返回其他值
    
//和它反过来的,将32位整数转换为点分十进制形式IP地址字符串
//成功返回非空指针,失败返回NULL
const char *inet_ntop(int af, const void * src, char * dst, socklen_t size)   

示例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h> //exit
#include <string.h> //bzero
#include <strings.h> //strncasecmp 忽略大小写比较
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERV_PORT (5001)
#define SERV_IP   "127.0.0.1"
#define BUFSIZE   (128)

int main(void)
{
    int sock_fd;
    struct sockaddr_in sin;

	//1.创建
    if( (sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1 )
    {
        perror("socket");
        exit(1);
    }
	//2.绑定 需要先配置sockaddr_in
    bzero(&sin,sizeof(sin));//清零再填充 
    sin.sin_family = AF_INET;//IPV4
    sin.sin_port   = htons(SERV_PORT);//端口号转换网络字节序
    //两种IP地址转换
    //sin.sin_addr.s_addr = inet_addr(SERV_IP);
    if( inet_pton(AF_INET,SERV_IP,(void*)&sin.sin_addr) != 1 ) 
    {
        perror("inet_pton");
        exit(1);
    }
    if(bind(sock_fd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
    {
        perror("bind");
        exit(1);
    }

三、监听套接字

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

//监听套接字,server端将主动套接字--->被动套接字
int listen(int sockfd, int backlog)

params: backlog 一般填5 指同时允许几路客户端和服务器进行正在连接(正在三次握手)ARM最大为8
    
return:成功返回0,出错返回-1 

内核中服务器的套接字fd会维护2个链表:

  • 正在三次握手的客户端链表(数量=2*backlog+1)

  • 已经建立好连接的客户端链表(已完成三次握手且分配好了newfd)

四、等待套接字

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

//阻塞等待客户端连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
    
params: addr 	客户端信息的指针(用户传入,连接完成内核保存进去),不需要的话填NULL
		addrlen 客户端信息长度的指针,不需要的话填NULL
    
return: 成功返回已建立连接的新的客户端描述符newfd(非负整数)
    	出错返回-1

五、服务器端示例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h> //exit
#include <string.h> //bzero
#include <strings.h> //strncasecmp 忽略大小写比较
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERV_PORT (5001)
#define SERV_IP   "127.0.0.1"
#define BUFSIZE   (128)
#define QUIT_STR  "quit"

int main(void)
{
    int sock_fd,newfd;
    struct sockaddr_in sin;
    char rd_buf[BUFSIZE];
    int read_num;

	//1.创建
    if( (sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1 )
    {
        perror("socket");
        exit(1);
    }
	//2.绑定 需要先配置sockaddr_in
    bzero(&sin,sizeof(sin));//清零再填充 
    sin.sin_family = AF_INET;//IPV4
    sin.sin_port   = htons(SERV_PORT);//端口号转换网络字节序
    //两种IP地址转换
    //sin.sin_addr.s_addr = inet_addr(SERV_IP);
    if( inet_pton(AF_INET,SERV_IP,(void*)&sin.sin_addr) != 1 ) 
    {
        perror("inet_pton");
        exit(1);
    }
    if(bind(sock_fd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
    {
        perror("bind");
        exit(1);
    }
    //3.监听
    if( listen(sock_fd,5) == -1 )
    {
        perror("listen");
        exit(1);
    }
    //4.阻塞等待客户端连接 连接成功后需要newfd
    if( (newfd = accept(sock_fd,NULL,NULL)) == -1 )
    {
        perror("accept");
        exit(1);
    }
	while(1)
    {
        bzero(rd_buf,BUFSIZE); //读之前先请零
        do
        {
            read_num = read(newfd,rd_buf,BUFSIZE-1);
        }while(read_num < 0 && errno == EINTR);
        if(read_num < 0)
        {
            perror("read");
        }
        if(read_num == 0) //客户端关闭
        {
            printf("Client is closed!\n");
            break;
        }
        printf("Server received:%s\n",rd_buf);
        if( strncasecmp(rd_buf,QUIT_STR,strlen(rd_buf)) == 0)//客户端输入quit
        {
            printf("Client is exiting!\n");
            break;
        }
    }
    close(newfd);.	
    close(sock_fd);

    return 0;
}

六、连接套接字

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

//连接服务器创建的套接字----客户端用
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    
params: //参数同bind----表示配置方式相近   
    
return: 成功返回0,出错返回-1

七、客户端示例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h> //exit
#include <string.h> //bzero
#include <strings.h> //strncasecmp 忽略大小写比较
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERV_PORT (5001)
#define SERV_IP   "127.0.0.1" //只能用ifconfig查到的
#define BUFSIZE   (128)
#define QUIT_STR  "quit"

int main(void)
{
    int sock_fd;
    struct sockaddr_in cin;
    char wr_buf[BUFSIZE];

    //1.创建
    sock_fd = socket(AF_INET,SOCK_STREAM,0);
    if(sock_fd == -1)
    {
        perror("socket");
        exit(1);
    }
    //2.连接服务器,还是一样先配置sockaddr_in
    bzero(&cin,sizeof(cin));
    cin.sin_family = AF_INET;
    cin.sin_port   = htons(SERV_PORT);
    //cin.cin_addr.s_addr = inet_addr(SERV_IP); 
    if( inet_pton(AF_INET,SERV_IP,(void*)&cin.sin_addr) != 1 )
    {
        perror("inet_pton");
        exit(1);
    }
    if( connect(sock_fd,(struct sockaddr*)&cin,sizeof(cin)) == -1 )
    {
        perror("connect");
        exit(1);
    }

    //3.写数据
    while(1)
    {
        bzero(wr_buf,BUFSIZE);//写之前先清零
        if( fgets(wr_buf,BUFSIZE-1,stdin) == NULL)
        {
            continue;//重新获取
        }
        write(sock_fd,wr_buf,strlen(wr_buf));//写数据--相当于发送给服务器

        if( strncasecmp(wr_buf,QUIT_STR,strlen(QUIT_STR)) == 0) //输入了quit
        {
            break;
        } 
    }

    close(sock_fd);
    return 0;
}

八、Send和Recv

1.网络发送数据

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags)
    
params: 前三个参数同write
    	flags	一般填0---此时同write
    			MSG_DONTWAIT 非阻塞版本
    			MSG_OOB 用于发送TCP类型的带外数据(out-of-band)

2.网络接收数据

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

ssize_t read(int sockfd, const void *buf, size_t len, int flags)
    
params: 前三个参数同read
    	flags	一般填0---此时同read
    			MSG_DONTWAIT 非阻塞版本
    			MSG_OOB 用于发送TCP类型的带外数据(out-of-band)
    			MSG_PEEK 偷看数据,读完不清除,非常实用
    
return: 成功返回收到字节数,失败返回-1
相关推荐
bohu834 分钟前
sentinel学习笔记1-为什么需要服务降级
笔记·学习·sentinel·滑动窗口
HE102910 分钟前
威尔克斯(Wilks)分布
学习
希雅不是希望23 分钟前
Ubuntu命令行网络配置
网络·ubuntu·php
kaixin_learn_qt_ing42 分钟前
Debian和Ubuntu
运维·ubuntu·debian
带电的小王44 分钟前
Docker在Ubuntu上安装
ubuntu·docker
QT.qtqtqtqtqt1 小时前
攻防世界easyphp
linux·运维·服务器
初学者7.1 小时前
Webpack学习笔记(3)
笔记·学习·webpack
菜鸟康1 小时前
Linux系统编程——系统内核中的信号
linux·运维·服务器
bohu832 小时前
sentinel学习笔记5-资源指标数据统计
笔记·sentinel·statisticslot
璞~2 小时前
MQTT 课程概览 (学习笔记)02
笔记·学习