Linux网络编程-2.TCP通信

2.Socket编程

2.1 Socket编程基础

Socket

  • 网络通信的函数接口
  • 封装了传输层协议
    – TCP
    – UDP

2.1.1 Socket套接字

创建一个套接字得到的是一个文件描述符
套接字

  • 创建成功,得到一个文件描述符fd
  • fd操作的是一块内核缓冲区

默认是阻塞状态
套接字通讯原理示例:
在这里插入图片描述

2.1.2 网络字节序

小端法:高位存高地址、低位存低地址
大端法:高位存低地址、低位存高地址
网络数据流采用大端字节序

网络字节序和主机字节序的转换:

#include <arpa/inet.h>

/* 将主机字节转换为网络字节 */
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);

/* 将网络字节转换为主机字节 */
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

IP地址转换函数:

#include <arpa/inet.h>

/*
 * 函数功能:
 *		将本地字节序IP地址转换为网络字节序IP地址
 * 参数:
 *		af:IP地址类型(AF_INET、AF_INET6)
 *		src:IP地址(点分十进制)
 *		dst:转换后的网络字节序IP地址(传出)
 *	返回值:
 *		成功:1
 *		异常:0(无效IP地址)
 *		失败:-1
 */
int inet_pton(int af, const char *src, void *dst);

/*
 * 函数功能:
 * 		将网络字节序IP地址转换为本地字节序IP
 * 参数:
 * 		af:IP地址类型(AF_INET、AF_INET6)
 * 		src:网络字节序IP地址
 * 		dst:转换后的本地字节序IP地址(传出)
 * 		size:dst大小
 * 返回值:
 *		成功:dst
 *		失败:NULL
 */
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

2.2 Socket相关函数

2.2.1 sockaddr数据结构

在这里插入图片描述

struct sockaddr {
	sa_family_t sa_family; 		/* address family, AF_xxx */
	char sa_data[14];			/* 14 bytes of protocol address */
};

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 */
};

struct sockaddr_in6 {
	unsigned short int sin6_family; 	/* AF_INET6 */
	__be16 sin6_port; 					/* Transport layer port # */
	__be32 sin6_flowinfo; 				/* IPv6 flow information */
	struct in6_addr sin6_addr;			/* IPv6 address */
	__u32 sin6_scope_id; 				/* scope id (new in RFC2553) */
};

2.2.2 建立一个socket通信socket()

#include <sys/types.h>
#include <sys/socket.h>

/*
 * 函数功能:
 * 		建立一个socket通信
 * 参数:
 * 		domain:IP地址类型(常用:AF_INET/AF_INET6/AF_UNIX)
 * 		type:
 * 			SOCK_STREAM	提供基于连接双向连续且可信赖的数据流,即TCP
 * 			SOCK_DGRAM	使用不连续不可信的数据报连接
 * 		protocol:type的代表协议,通常为0
 * 返回值:
 * 		成功:新的fd
 * 		失败:-1, 并设置errno
 */
int socket(int domain, int type, int protocol);

/* 示例 */
socket(AF_INET, SOCK_STREAM, 0);

2.2.3 绑定socketbind()

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/*
 * 函数功能:
 * 		给socket绑定一个地址结构(IP+Port)
 * 参数:
 * 		sockfd:socket()返回的socket文件描述符
 * 		addr:(struct sockaddr *)&addr(地址结构,传入)
 * 			struct sockaddr_in addr;
 * 			addr.sin_family = AF_INET;
 * 			addr.sin_port = htons(port_num);
 * 			addr.sin_addr.s_addr = htonl(INADDR_ANY);
 * 				htonl(INADDR_ANY)	取出系统中有效的任意IP地址(二进制类型)
 * 		addrlen:addr的大小
 * 返回值:
 * 		成功:0
 * 		失败:-1, 并设置errno
 */
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

2.2.4 设置连接上限listen()

#include <sys/types.h>
#include <sys/socket.h>
/*
 * 函数功能:
 * 		设置同时与服务器建立连接的上限数
 * 参数:
 * 		sockfd:socket()返回的socket文件描述符
 * 		backlog:上限数值(max=128)
 * 返回值:
 * 		成功:0
 * 		失败:-1, 并设置errno
 */
int listen(int sockfd, int backlog);

2.2.5 服务器建立连接accept()

#include <sys/types.h>
#include <sys/socket.h>

/*
 * 函数功能:
 * 		阻塞等待客户端建立连接
 * 参数:
 * 		sockfd:socket()返回的socket文件描述符
 *		addr:成功与服务器建立连接的那个客户端的地址结构(传出)
 * 		addrlen:
 * 			传入:addr的大小
 * 			传出:客户端addr的实际大小
 * 返回值:
 * 		成功:与客户端建立连接的socket文件描述符
 * 		失败:-1, 并设置errno
 */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

2.2.6 客户端建立连接connect()

#include <sys/types.h>
#include <sys/socket.h>

/*
 * 函数功能:
 * 		客户端与服务器建立连接
 * 参数:
 * 		sockfd:socket()返回的socket文件描述符
 *		addr:服务器的地址结构(传入)
 * 		addrlen:addr的大小
 * 返回值:
 * 		成功:0
 * 		失败:-1, 并设置errno
 * 如果不使用bind()绑定客户端地址结构,采用“隐式绑定”
 */
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

2.3 Socket创建流程

在这里插入图片描述

2.3.1 Server服务器端

Socket TCP Server:

  • 创建套接字
    int lfd = socket()
  • 绑定本地IP和端口
    strcut sockaddr_in serv;
    serv.sin_family = AF_INET/AF_INET6
    ser.sin_port = htons(port);
    sserv.sin_addr.s_addr = htonl(INADDR_ANY)
    bind(lfd, &serv, sizeof(serv));
  • 设置监听上限
    listen(lfd, 128);
  • 等待接受连接请求
    struct sockaddr_in client;
    int len = sizeof(client);
    int cfd = accept(lfd, &client, &len);
    cfd用于通信
  • 通信
    – 接收数据:read()
    – 发送数据:write()
  • 关闭
    close(lfd);
    close(cfd);

Server示例代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

int main(int argc, const char *argv[]) {
    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd == -1) {
        perror("socket error");
        exit(1);
    }

    // 2. lfd 和本地的IP port绑定
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;    // 地址族协议 - ipv4
    server.sin_port = htons(8888);	// 设置端口
    server.sin_addr.s_addr = htonl(INADDR_ANY);	//设置IP地址
    int ret = bind(lfd, (struct sockaddr *) &server, sizeof(server));
    if (ret == -1) {
        perror("bind error");
        exit(1);
    }

    // 3. 设置监听
    ret = listen(lfd, 128);
    if (ret == -1) {
        perror("listen error");
        exit(1);
    }

    // 4. 等待并接收连接请求
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int cfd = accept(lfd, (struct sockaddr *) &client, &len);
    if (cfd == -1) {
        perror("accept error");
        exit(1);
    }

    printf(" accept successful !!!\n");
    char ipbuf[64] = {0};
    printf("client IP: %s, port: %d\n",inet_ntop(AF_INET, &client.sin_addr.s_addr,ipbuf, sizeof(ipbuf)),
           ntohs(client.sin_port));
    // 5. 一直通信
    while (1) {
        // 先接收数据
        char buf[BUFSIZ] = {0};
        int len = read(cfd, buf, sizeof(buf));
        if (len == -1) {
            perror("read error");
            exit(1);
        } else if (len == 0) {
            printf(" 客户端已经断开了连接 \n");
            close(cfd);
            break;
        } else {
            printf("recv buf: %s\n", buf);
            // 转换 - 小写 -> 大写
            for (int i = 0; i < len; ++i) {
                buf[i] = toupper(buf[i]);
            }
            printf("send buf: %s\n", buf);
            write(cfd, buf, len);
        }
    }
	
	// 6. 关闭
    close(lfd)return 0;
}

服务器端运行结果:
在这里插入图片描述
使用nc 127.0.0.1 8888:连接服务器
在这里插入图片描述

2.3.2 Client客户端

客户端

  • 创建套接字
    int fd = socket()
  • 连接服务器
    connect();
  • 通信
    – 接收数据:read()
    – 发送数据:write()
  • 断开连接
    close(fd);

Client示例代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

#define SERV_PORT 8888

int main(int argc, const char* argv[])
{
	// 1. 创建套接字
	int cfd = socket(AF_INET, SOCK_STREAM, 0);
	if(cfd == -1)
	{
		perror("socket error:");
		exit(-1);
	}
	// 2. 连接服务器
	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
	int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
	if( ret == -1 )
	{
		perror("connect error:");
		exit(-2);
	}
	char buf[BUFSIZ] = { 0 };
	while(1)
	{
		write(cfd, "hello", 5);
		sleep(1);
		ret = read(cfd, buf, sizeof(buf));
		if(ret <= 0)
			break;
		write(STDOUT_FILENO, buf, ret);
	}
	close(cfd);
	return 0;
}

2.4 数据收发

socket编程中flags一般为0

2.4.1 数据接收

ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

2.4.2 数据发送

ssize_t write(int fd, const void *buf, size_t count);
ssize_t write(int sockfd, const void *buf, size_t len, int flags);

2.5 半关闭

A给B发送lFIN(调用了close函数),但是B没有给A发送FIN(B没有调用close)
A不能给B发送数据,A可以接收B发送的数据
函数:

  • int shutdown(int sockfd, int how);

sockfd:要半关闭的一方对应的文件描述符
how:

  • SHUT_RD:0-read
  • SHUT_WR:1-write
  • SHUT_RDWR:2-read&write

2.6 查看网络状态信息netstat

参数说明
a(all)显示所有选项,默认不显示LISTEN相关信息
p显示建立相关链接的程序名
n拒绝显示别名,能显示数字的全部转化为数字
t(tcp)仅显示tcp相关选项
u(udp)仅显示udp相关选项
l仅列出有在Listen(监听)的服务状态

2.7 端口复用

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
# 设置方法、需要在绑定端口前设置
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT灰猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值