TCP网络编程

TCP头部格式说明

* 源端口和目的端口:  各占 2 字节.端口是传输层与应用层的服务接口.传输层的复用和分用功能都要通过端口才能实现
* 序号:   Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
* 确认号:   Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
* 数据偏移/首部长度:  占 4 位,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远.“数据偏移”的单位是 32 位字(以 4 字节为计算单位)
* 保留:  占 6 位,保留为今后使用,但目前应置为 0
* 紧急URG:  当 URG=1 时,表明紧急指针字段有效.它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)
* 确认ACK:  只有当 ACK=1 时确认号字段才有效.当 ACK=0 时,确认号无效。   不要将确认序号Ack与标志位中的ACK搞混了。
* PSH(PuSH):  接收 TCP 收到 PSH = 1 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付
* RST (ReSeT):  当 RST=1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接
* 同步 SYN:  同步 SYN = 1 表示这是一个连接请求或连接接受报文
* 终止 FIN:  用来释放一个连接.FIN=1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接
* 检验和:  占 2 字节.检验和字段检验的范围包括首部和数据这两部分.在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部
* 紧急指针:  占 16 位,指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)
* 选项:  长度可变.TCP 最初只规定了一种选项,即最大报文段长度 MSS.MSS 告诉对方 TCP:“我的缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节.” [MSS(Maximum Segment Size)是 TCP 报文段中的数据字段的最大长度.数据字段加上 TCP 首部才等于整个的 TCP 报文段]
* 填充:  这是为了使整个首部长度是 4 字节的整数倍
网络通信模型


网络编程流程



1.服务器端
①首先调用socket( )---成功后返回一个文件描述符fd
     int socket(int family, int type, int protocol);
        family指定协议族;type参数指定socket的类型:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;protocol通常赋值"0"; 

②bind( )指定监听ip和目的端口号
     int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
        sockfd是一个socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的针; addrlen常被设置为sizeof(struct sockaddr)。 最后,对于bind 函数要说明的一点是,你可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号: 
         my_addr.sin_port = 0;    /* 系统随机选择一个未被使用的端口号 */ 
        my_addr.sin_addr.s_addr = INADDR_ANY;   /* 填入本机IP地址 */ 
    通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。bind()函数在成功被调用时返回0;遇到错误时返回"-1"并将errno置为相应的错误号。另外要注意的是,当调用函数时,一般不要将端口号置为小于1024的值,因为1~1024是保留端口号,你可以使用大于1024中任何一个没有被占用的端口号。

③listen( )设定最大连接数,超过最大连接数会返回错误
     int listen(int sockfd, int backlog); 
        sockfd是socket系统调用返回的服务器端socket描述符;backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们。backlog对队列中等待服务的请求的数目进行了限制,大多数系统缺省值为20。当listen遇到错误时返回-1,errno被置为相应的错误码。   

int accept(int sockfd, struct sockaddr *addr, int *addrlen); 
    sockfd是被监听的服务器socket描述符,addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求的客户端地址;addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。错误发生时返回一个-1并且设置相应的errno值。accept()函数将返回一个新的socket描述符,来供这个新连接来使用,在新的socket描述符上进行数据send()和recv()操作。

⑤close()和shutdown()——结束数据传输 
    当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:
    int shutdown(int sockfd,int how); 
        sockfd的含义是显而易见的,而参数 how可以设为下列值: 
  ·0-------不允许继续接收数据 
  ·1-------不允许继续发送数据 
  ·2-------不允许继续发送和接收数据,均为允许则调用close() 
    shutdown在操作成功时返回0,在出现错误时返回-1(并置相应errno)。

2.客户端
① connect()函数用来与远端服务器建立一个TCP连接其函数原型为: 
    int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 
        sockfd是目的服务器的sockt描述符;serv_addr是服务器端的IP地址和端口号的地址。遇到错误时返回-1,并且errno中包含相应的错误码。进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,内核会自动选择一个未被占用的端口供客户端来使用。
    服务器端一旦调用accept(),只要客户端不调用connect(),会一直造成阻塞,知道客户端调用connect(),并且重新生成一个fd。    

②bind()不调用时系统内核会自动分配一个可用端口,调用时可以设定一个指定端口。

服务器端程序
本程序代码
/*********************************************************************************
 *      Copyright:  (C) 2018 Yujie
 *                  All rights reserved.
 *
 *       Filename:  socket_server.c
 *    Description:  server.c
 *                 
 *        Version:  1.0.0(04/21/2018)
 *         Author:  yanhuan <yanhuanmini@foxmail.com>
 *      ChangeLog:  1, Release initial version on "04/21/2018 09:21:24 PM"
 *                 
 ********************************************************************************/
/* 
  1、创建套接字---socket
     处理地址和端口
  2、套接字绑定本地的地址和端口---band
  3、套接字设定监听状态(等待客服端消息)---listen
  4、接受消息,返回一个用连接的新套接字
  5、新套接字通信---read,write
  6、关闭套接字---close
*/

#include <sys/socket.h>  //socket头文件
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define port     12000
#define buf_size 1024


int main (int argc, char **argv)
{
    int                      listen_fd, accept_fd = 1;  //服务器端需要两个套接字:一个开启连接,一个通信
    struct sockaddr_in       server_addr;              //服务器端地址,是一个结构体,等下要填充ipV4或者ipV6的结构
    char                     buf[buf_size];
    
    listen_fd = socket(AF_INET,SOCK_STREAM,0);       //创建套接字,ipV4地址,可靠的面向连接的字符流,最后0是默认协议
    if(listen_fd < 0)
    {
        printf("Create socket failure: %s\n", strerror(errno));  //如果创建失败,打印错误信息
        return -1;
    }
    printf("Socket create fd[%d]\n",listen_fd);      //如果成功,返回fd
   
  //服务器设置,设置server地址和端口号,用于接受任何client端
    memset(&server_addr,0,sizeof(server_addr));       //清空再设置ipV4地址
    server_addr.sin_family = AF_INET;                //设置为ipV4地址
    server_addr.sin_port = htons(port);             //端口号,主机字节序转网络字节序,short是16位
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //设置host主机地址,INADDR_ANY是指定为0.0.0.0的地址,表示不确定地址,或指所有地址
//  server_addr.sin_addr.s_addr = htonl(ip);    
    if( bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0 )
    {
        printf("Bind socket failure: %s\n", strerror(errno));  //如果绑定失败,打印错误信息
        return -2;
    }
    printf("Socket bind ok \n");
    
    listen(listen_fd,15);               //等待连接,最大连接数设为15 
    printf("Socket listen ok \n");
    
    while(1)
    {
        printf("Start accept %d...\n",listen_fd);
        accept_fd = accept(listen_fd,NULL,NULL);
        //accept接受的参数必须经过listen和bing处理过,返回一个新的socket码
        //新的socket传送数据,旧的socket还能继续用accept接受请求
        //后面两个参数设置为NULL,那么会在系统连接时自动根据客户端信息填入
        if(accept_fd < 0)
        {
            printf("Accept socket failure: %s\n", strerror(errno));  //如果监听失败,打印错误信息
            return -1;  
        }
        printf("Socket Accept fd[%d]\n",accept_fd);      //如果成功,返回fd
     
        memset(buf, 0, sizeof(buf)); 
        
        read(accept_fd,buf,sizeof(buf));				//从套接字read
        printf("From client read : %s\n",buf);
		
        write(accept_fd,"I have received the message.",strlen("I have received the message."));	//写回给套接字
        sleep(1);

        close(accept_fd);
    }

    close(listen_fd);

    return 0;
} /* ----- End of main() ----- */
客户端程序

本程序代码

/*********************************************************************************
 *      Copyright:  (C) 2018 Yujie
 *                  All rights reserved.
 *
 *       Filename:  socket_client.c
 *    Description:  client.c
 *                 
 *        Version:  1.0.0(04/26/2018)
 *         Author:  yanhuan <yanhuanmini@foxmail.com>
 *      ChangeLog:  1, Release initial version on "04/26/2018 07:16:03 PM"
 *                 
 ********************************************************************************/
/* 
	1、创建套接字---socket
	2、发送连接请求---connect
	3、连接后,进行通信---read,write
	4、释放套接字---close
*/

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define port	 12000
#define buf_size 1024
#define ip		 192.168.109.1


int main(int argc, char **argv)
{
	int					connect_fd = -1;	//客户端只需要一个套接字:用来连接服务器
	struct sockaddr_in	server_addr;		//指定服务器端地址,是一个结构体,等下要填充ipV4或者ipV6的结构
	char 				buf[buf_size];

	connect_fd = socket(AF_INET,SOCK_STREAM,0);//创建连接套接字,ipV4地址,可靠的面向连接的字符流,最后0是默认协议
	if(connect_fd < 0)
	{
		printf("Create socket failure: %s\n", strerror(errno));  //如果创建失败,打印错误
		return -1;
	}
	printf("Socket create fd[%d]\n",connect_fd);      //如果成功,返回connect_fd
	
	//连接服务器的准备,要连接的server地址和端口号
	memset(&server_addr,0,sizeof(server_addr));       //清空再设置ipV4地址
	server_addr.sin_family = AF_INET;                //设置为ipV4地址
	server_addr.sin_port = htons(port);              //端口号,主机字节序转网络字节序,short是16位
	//BSD网络软件包含inet_addr和inet_ntoa和inet_aton,用于二进制地址和‘.’分十进制字符表示,只适用于ipV4地址,即addr需要存放32位地址
	//另外提供两个新函数:inet_ntop和inet_pton同时支持ipV4和ipV6,即addr需要存放128位地址
	
//	inet_aton("ip",&server_addr.sin_addr);
	inet_aton("127.0.0.1",&server_addr.sin_addr);	//127.0.0.1被称为回环地址,默认被看成永不宕机的接口,不需要网卡就可ping同这个本地回环地址,一般用来检查本地网络协议、基本数据是否正常
	

	//连接server端
	if(connect(connect_fd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
	{	
		printf("connect to server failure: %s\n", strerror(errno));
		return -1;
	}

	write(connect_fd,"Hello!This message is from client.\n",strlen("Hello!This message is from client.\n"));

	memset(buf,0,sizeof(buf));
	read(connect_fd,buf,sizeof(buf));
	printf("read  '%s' from client\n",buf);
	sleep(1);
		
	close(connect_fd);	

	return 0;
}

注意:先开服务器程序(会阻塞),再开客服端程序。

本程序是最简单的TCP网络协议编程,服务器端一直等待,只能接受一个客户端信息,客户端只能运行一次

解决上述的缺点,需要用到多路复用(select、poll、epoll)或多进程、多线程,将在下一阶段优化。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值