Linux下socket编程:TCP连接

目录

一、流程简述
    服务器端
    客户端

二、概念及函数
    1.套接字描述符
    2.socket函数
    3.bind函数
    4.connect函数
    5.listen函数
    6.accept函数
    7.close函数

三、代码
    服务端
    客户端

四、笔记
    1.一个标准的三次握手抓包
    ​2.小技巧:打印出connect过程的错误


一、流程简述

服务器端

创建套接字(socket) -> 绑定socket和端口号(bind) -> 监听该端口(listen) -> 接收来自客户端的连接请求(accept) -> 读写(recv\read...) -> 关闭(close)

客户端

创建套接字(socket) -> 连接指定计算机的端口(connect) -> 读写(recv\read...) -> 关闭(close)

 

二、概念及函数

1.套接字描述符

套接字是通信端点的抽象,两个端口之间通过套接字建立连接进行通信,通过socket函数可以创建一个套接字,函数返回值为套接字描述符,套接字描述符有点类似文件描述符,也可以通过read和write函数对其读写操作。

2.socket函数

    头文件 #include <sys/socket.h>
    函数体 int socket (int domain, int type, int protocol);
    返回值 成功返回套接字描述符,出错返回-1
        domain 为域,用来分辨地址类型,常用就是AF_INET 和AF_INET6,如果用AF_UPSPEC则protocol参数需要指定
            AF_INET ipv4因特网域
            AF_INET6 ipv6因特网域
            AF_UNIX UNIX域
            AF_UPSPEC 未指定
        type用来确定套接字类型,通畅udp选择SOCK_DGRAM ,tcp选择SOCK_STREAM
            SOCK_DGRAM 固定长度无连接的不可靠报文传递
            SOCK_RAW ip协议的数据报接口
            SOCK_SEQPACKET 固定长度的,有序的,可靠的,面向连接的报文传递
            SOCK_STREAM 有序的,可靠的,双向的,面向连接的字节流
        protocol通常是0,表示为给定的域domain参数和套接字类型type参数选择默认协议,在AF_INET 域中,类型为SOCK_STREAM的默认协议为IPPROTO_TCP。在AF_INET 域中,类型为SOCK_DGRAM 的默认协议为IPPROTO_UDP。尾缀就代表的协议
            IPPROTO_IP
            IPPROTO_IPV6
            IPPROTO_ICMP
            IPPROTO_RAW
            IPPROTO_TCP
            IPPROTO_UDP

3.bind函数

    头文件 #include <sys/socket.h>
    函数体 int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
    返回值 成功返回0,出错返回-1
        sockfd  为套接字描述符
        addr  为指向地址结构的指针
        len  为地址结构的长度
    ipv4的地址结构用sockaddr_in表示
        typedef uint16_t in_port_t ;
        typedef uint32_t in_addr_t;
      struct in_addr{
        in_addr_t s_addr;
      };
      struct sockaddr_in{
        sa_family_t sin_family;
       in_port_t sin_port;
       struct in_addr sin_addr;
      };

4.connect函数

    头文件 #include <sys/socket.h>
    函数体 int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
    返回值 成功返回0,出错返回-1
    参数同bind函数
        注意:如果是非阻塞状态下,调用connect函数会直接返回-1,错误代码115

5.listen函数

    头文件 #include <sys/socket.h>
    函数体 int listen(int sockfd, int backlog);
    返回值 成功返回0,出错返回-1
        backlog规定为连接请求(connect)队列的最大个数,注意不是tcp连接的最大数量,是未完成连接的最大请求数量。

6.accept函数

    头文件 #include <sys/socket.h>
    函数体 int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
    返回值 成功返回一个新的套接字描述符(原来的套接字描述符可以继续用于accept函数的侦听),出错返回-1
        addr 客户端地址内容
        len 接收客户端地址内容的长度

      如果不关心客户端的标识(地址),可以将addr和len设为NULL,不然需要设置足够的空间来存储这些数据。
      如果没有连接请求,accept会阻塞直到一个请求的到来。如果socket设置为非阻塞模式,会直接返回-1。

7.close函数

    头文件 #include <unistd.h>
    函数体 int close(int fd);
    返回值 成功返回0,出错返回-1

 

三、代码

一个特别特别简单的tcp连接代码,为了熟悉这个流程

注意htons,htonl,ntohs,inet_pton,inet_ntop等函数的使用

服务端

/*server*/
#include<stdio.h>
#include<unistd.h> 
#include <sys/socket.h>
#include <netinet/in.h>
/* 
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t; 
struct in_addr{
	in_addr_t s_addr;
};
struct sockaddr_in{
	sa_family_t sin_family;
	in_port_t sin_port;
	struct in_addr sin_addr;
};*/
int main()
{
	int sockfd = 0;
	int newsockfd = 0;
	int ret = 0;
	unsigned int addr=0;
	unsigned int client_sock_len=sizeof(struct sockaddr_in);
	struct sockaddr_in sock;
	struct sockaddr_in client_sock;
	char send_str[]="Hello client!\n";
	char recv_str[100];
	char client_addr_str[15];
	
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		printf("socket error\n");
		return 0;
	}
	
	sock.sin_family = AF_INET;
	sock.sin_port = htons(2000);//必须使用htons转换 
	/*将字符串的ip地址转化为网络字节序的整形*/
	if(inet_pton(AF_INET,"192.168.206.128",(void *)&addr)!= 1)
	{
		printf("inet_pton addr error\n");
		return 0;
	}
	sock.sin_addr.s_addr = addr;
	ret = bind(sockfd,(struct sockaddr *)&sock,sizeof(struct sockaddr_in));
	if(ret < 0)
	{
		printf("bind error\n");
		return 0;
	}
	
	ret = listen(sockfd,5);
	if(ret < 0)
	{
		printf("listen error\n");
		return 0;
	}
	
	newsockfd = accept(sockfd,(struct sockaddr *)&client_sock,&client_sock_len);
	if(newsockfd < 0)
	{
		printf("accept error\n");
		return 0;
	}
	/*将网络字节序的整形转化为字符串格式的ip地址*/
	if(inet_ntop(AF_INET,(void *)&(client_sock.sin_addr),client_addr_str,sizeof(client_addr_str)) == NULL)
	{
		printf("inet_ntop addr error\n");
		return 0;
	}
	printf("newsockfd = %d,oldsockfd = %d\n",newsockfd,sockfd);
	/*客户端的ip和端口号*/
	printf("client connect success\n address = %s,port = %d \n",client_addr_str,ntohs(client_sock.sin_port));
	/*发送数据*/
	if(send(newsockfd,send_str,sizeof(send_str),0) == -1)
	{
		printf("send error\n");
		return 0;
	}
	/*接收数据*/
	if(recv(newsockfd,recv_str,sizeof(recv_str),0) > 0)
	{
		printf("S recv:%s\n",recv_str);
	}

	close(sockfd);
	close(newsockfd);
	return 0;
}

客户端

/*client*/
#include<stdio.h>
#include<unistd.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
int main()
{
	int sockfd = 0;
	int ret = 0;
	struct sockaddr_in sock;
	unsigned int addr=0;
	char send_str[]="Hello service!\n";
	char recv_str[100];
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		printf("socket error\n");
		return 0;
	}
	
	sock.sin_family = AF_INET;
	sock.sin_port = htons(2000);
	if(inet_pton(AF_INET,"127.0.0.1",&addr)!= 1)
	{
		printf("inet_pton addr error\n");
		return 0;
	}
	sock.sin_addr.s_addr = addr;
	ret = connect(sockfd,(struct sockaddr_in *)&sock,sizeof(struct sockaddr_in));
	if(ret < 0)
	{
		printf("connect error=%d %s\n",errno,strerror(errno));//这个可以打印出错误码和原因
		return 0;
	}
	if(send(sockfd,send_str,sizeof(send_str),0) == -1)
	{
		printf("send error\n");
		return 0;
	}
	if(recv(sockfd,recv_str,sizeof(recv_str),0) > 0)
	{
		printf("C recv:%s\n",recv_str);
	}
	close(sockfd);
	return 0;
}

四、笔记

1.一个标准的三次握手抓包

    第一次握手:客户端发送syn包(syn=j)到服务器。 
    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个ASK包(ask=k)。 
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)。 
    三次握手完成后,客户端和服务器就建立了tcp连接,这时可以调用accept函数获得此连接

2.小技巧:打印出connect过程的错误

        #include <errno.h>
        #include <string.h>
        printf("connect error=%d %s\n",errno,strerror(errno));

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值