TCP通信

本文详细介绍了TCP通信的基础知识,包括Socket的创建、绑定、监听、接受连接以及数据的发送和接收。通过C/S架构展示了服务器和客户端的生命周期,讲解了如何使用socket()、bind()、listen()、accept()、recv()和send()等函数进行TCP通信。示例代码展示了服务器如何监听并接受客户端连接,以及客户端如何发起连接并进行数据交互。
摘要由CSDN通过智能技术生成

TCP通信

Socket 起源于 Unix,而Unix基本哲学之一就是一切皆文件,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符,下图就是基于C/S架构的tcp通信模型
在这里插入图片描述
server的生命周期:

  1. 创建socket
  2. 绑定地址,端口
  3. 监听网络连接
  4. 接收连接
  5. 关闭连接

clinet的生命周期

  1. 创建socket
  2. 绑定地址
  3. 发起连接
  4. 关闭连接

server服务器端

首先需要使用sockaddr_in来定义地址

struct sockaddr_in {
	sa_family_t		sin_family;	//地址族(Address Family)
	uint16_t		sin_port;	//16位TCP/UDP端口号
	struct in_addr	sin_addr;	//32位IP地址
	char			sin_zero[8] //清0操作
};

//另一个结构体
struct in_addr {
	In_addr_t		s_addr;		//32位IPv4地址
};

sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作.
使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。

1.socket()

socket()用来创建一个socket,

int socket(int domain,int type, int protocol)

返回值非负:成功 ;-1 : 失败
参数一 通常有 AF_INET, AF_INET6, AF_LOCAL等
在这里插入图片描述
参数二 是接口类型,就是流 TCP是SOCK_STREAM,UDP则是SOCK_DGRAM其他用法可以man一下查找
参数三 是和上面相匹配的,写0会自动匹配。

bind()

int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)

返回值为0:成功;-1:失败
当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。

参数一是之前定义的socket描述符
参数二制定了ip和端口号,在声明结构体之后通过(struct sockaddr *)&mysock就可以当参数
参数三是和前面结构体的长度相同的。

通常服务器在启动的时候都会绑定一个众所周知的协议地址,用于提供服务,客户就可以通过它来接连服务器;而客户端可以指定IP或端口也可以都不指定,未分配则系统自动分配。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

listen()

int listen(int sockfd,int backlog)

返回值:0:成功;-1:失败

参数一:socket描述符
参数二:最大连接数量(还有更规范的定义需要再查)

accept()

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

返回值 非负:成功;-1:失败
参数和bind()一样
一般来说,实现时accept()为阻塞函数,当监听socket调用accept()时,它先到自己的receive_buf中查看是否有连接数据包;若有,把数据拷贝出来,删掉接收到的数据包,创建新的socket与客户发来的地址建立连接;若没有,就阻塞等待;

recv()

ssize_t recv(int sockfd,void *buf, size_t len,int flags)

参数一:接收套接字描述符
参数二:指定缓冲区地址,接收储存数据
参数三:接收数据缓冲区长度
参数四:一般为0

表示从接收缓冲区拷贝数据。成功时,返回拷贝的字节数,失败返回-1。阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休眠状态。若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1,置错误码为EWOULDBLOCK。

send()

ssize_t send(int sockfd,constvoid *buf, size_t len,int flags)

参数一:socket描述符(非监听)
参数二:发送数据的缓存
参数三:发送的长度
参数四:一般为0

每一个tcp套接口都有一个发送缓冲区,调用send函数的过程,将内核将用户数据拷贝到tcp套接口的发送缓冲区的过程,len大于缓冲区大小则返回-1

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>

#include <netinet/in.h>
#include <malloc.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>

#include <sys/time.h>

#define SERVER_PORT_TCP 8888
#define TCP_BACKLOG 10

/* 在sock_fd 进行监听,在 new_fd 接收新的链接 */
int sock_fd, new_fd;


int main(int argc, char *argv[])
{
    /* 自己的地址信息 */
    struct sockaddr_in my_addr;

    /*	连接者的地址信息*/
    struct sockaddr_in their_addr;
    int sin_size;

    struct sockaddr_in *cli_addr;

    /* 创建socket*/
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket is erroe\n");
        exit(1);
    }

	/* 主机字节顺序 */
	/* 协议 */
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(SERVER_PORT_TCP);
	/* 当前IP 地址写入 */
    my_addr.sin_addr.s_addr = INADDR_ANY;

	/* 将结构体其余的都清零 */
    bzero(&(my_addr.sin_zero), 0);

    /* bind绑定*/
    if(bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
    {
        perror("bind is error\r\n");
		exit(1);
    }

    /*开始监听*/
    if(listen(sock_fd, TCP_BACKLOG) == -1)
    {
        perror("Listen is error\r\n");
        exit(1);
    }

    printf("start accept\n");

    /* accept() 循环 等待客户端接入*/
    while(1)
    {
        sin_size = sizeof(struct sockaddr_in);

        if((new_fd = accept(sock_fd, (struct sockaddr*)&their_addr, (socklen_t *)&sin_size)) == -1){
            perror("accept");
            continue;//客户端连接后,搞一个新的套接字
        }
        
		//处理目标
        ssize_t ret;
        char recvbuf[512];
        char *buf = "Hello! I'm server!";

        while(1)
        {
            if ((ret  = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == 1)
            {
                printf("recv error \r\n");
                return -1;
            }
            printf("recv :\r\n");
			printf("%s", recvbuf);//打印接收信息
            printf("\r\n");
            if((ret = send(new_fd, buf, strlen(buf) + 1, 0)) == -1)//发送buf到客户端
			{
				perror("send : ");
			}
        }
        close(new_fd);
    }
    return 0;
    
}

client客户端

客户端首先需要建立一个socket,然后进行连接后进行操作

connect

int connect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)

返回值 :0:成功;-1:失败
通过此函数建立于TCP服务器的连接,实际是发起三次握手过程,仅在连接成功或失败后返回。参数sockfd是本地描述符,addr为服务器地址,addrlen是socket地址长度。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <netdb.h>


#include <sys/time.h>

int sockfd;

#define SERVER_IP	"127.0.0.1"

#define SERVER_PORT	8888

int main(int argc, char *argv[])
{
	/*	连接者的主机信息 */
	struct sockaddr_in their_addr;	

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		/*	如果socket()调用出现错误则显示错误信息并退出 */
		perror("socket");
//		exit(1);
	}

	/*	主机字节顺序 */
	their_addr.sin_family = AF_INET;
	/*	网络字节顺序,短整型 */
	their_addr.sin_port = htons(SERVER_PORT);
	their_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
	/*	将结构剩下的部分清零*/
	bzero(&(their_addr.sin_zero), 8);
	if(connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
	{
		/*	如果connect()建立连接错误,则显示出错误信息,退出 */
		perror("connect");
		exit(1);
	}

	

	ssize_t ret;
	char recvbuf[512];
	char *buf = "hello! I'm client!";
	
	while(1)
	{
		
		if((ret = send(sockfd, buf, strlen(buf) + 1, 0)) == -1)
		{
			perror("send : ");
		}

		if((ret = recv(sockfd, &recvbuf, sizeof(recvbuf), 0)) == -1){
			return -1;
		}

		printf("recv :\r\n");
		printf("%s", recvbuf);
		printf("\r\n");
	}
	
	close(sockfd);

	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值