网络传输协议TCP的基础知识以及程序实现

SOCKET 实现TCP通信

学习知识对知识框架的把握是十分重要的,要知道学的知识点处于框架的哪个位置
所以对于网络知识,知识体系和框架非常重要,目前公认的网络模型有两种,一种是OSI七层模型,另一种是TCP/IP五层模型。对应关系如下
在这里插入图片描述
从上图中可以看出,OSI模型与TCP/IP模型之间是存在对应关系的,并且以传输层为分界线,传输层及以下完全一致,TCP/IP的网络接口层就是数据链路层和物理层的集合。
上述两个模型均为常用模型,但是相对而言,TCP/IP更为普遍。TCP中不同的层对应不同的协议
应用层:FTP(文件传输协议) , DNS ,HTTP , Telent , SMTP,
传输层: TCP UDP
网络层:IP ARP(地址解析协议) RARP ICMP(互联网控制信息协议)
数据链路层: 各种通信网络接口,以太网,物理网络等。

在这里插入图片描述
在这里插入图片描述

TCP协议的三次握手和四次挥手

所谓的三次握手和四次挥手,是指TCP连接和断开的过程。三次握手即连接需要三次数据交互的确定。四次挥手即指TCP断开时需要四次数据交互的确定。
以打电话为例:
三次握手:A给B打
A:喂,是B吗?
B:是的是的,你是A吗?
A:是的,是的,开始聊天吧

四次挥手:
A:今天就结束了吧。
B:好的
B:其实我也想结束
A:那就结束吧。

这里边值得注意的是,在四次挥手的时候A先提出了断开的请求,但实际断开并没有结束,在B确定了A的断开请求后,依然在继续对话。这是由于TCP的全双工(Full Duplex)传输特性。就是说虽然只有一个连接,但是存在A-B,B-A 两条路,就和双行公路是一样的。A发出断开请求,只是A到B的路断了,但是B到A 的路还通着。直到B提出断开请求,并且A确定后,连接才算彻底断开。

TCP的连接建立过程中是分为客户端和服务端的。通俗来讲,就就是主动端和被动端,服务器是被动端,只能等着别人来连接。客户端进行主动连接,连接的方式就是通过IP地址和端口号,一个服务器可以接受多个客户端的连接,但是一个客户端只能连接一台服务器。连接后服务端会自动划分内存区域以分配客户端的通信。至于端口,IP,内存的关系,如下图所示:
在这里插入图片描述

程序实现过程

TCP/IPP协议:
1. 叫传输控制/网际协议,又叫网络通信协议,
2. 套接字socket
在网络中用来描述计算机中不同程序与其他计算机程序的通信方式。socket其实是一种特殊的IO接口。也是一种文件描述符。套接字socket分为三类
流式socket(SOCK_STREAM):流式套接字提供可靠、面向连接的通信流,使用TCP协议,保证了数据的时序性和正确性。
数据报socket(SOCK_DGRAM):数据报套接字定义的是无连接服务,数据相对独立传输,不能保证时序性与正确性,使用数据包协议的是UDP。
原始socket:原始套接字允许对底层协议如IP或者ICMP进行访问
套接字由三个参数组成:IP地址,端口号,传输层协议。用以上三个参数判断不同进程间的网络通信与链接。
3. 套接字数据结构:
C语言编写socket程序时,通常会用到sockaddr和sockaddr_in两个结构体,用来保存套接字信息
结构体具体内容如下:
在这里插入图片描述
其中,对于sockaddr,一般编程中不会对此结构体进行操作,大部分情况用于bind,connect,sendto,recvfrom等函数的参数,会用sockaddr_in来代替。
常见的地址族有:AF_INET, AF_INET6AF_LOCAL
端口以及地址处理函数
(1)unit16_t_hons(unit16_t_host16bit)把16位值从主机字节序转到网络字节
(2)unit32_t_hons(unit32_t_host32bit)把32位值从主机字节序转到网络字节
(3)unit16_t_ntohs(unit16_t_net16bit)把16位值网络字节序转到主机字节序
(4)unit32_t_ntohs(unit32_t_net32bit)把32位值网络字节序转到主机字节序
4. 用socket进行TCP通信是常用函数以及所在头文件
(1)socket :建立一个socket连接
#include <sys/types.h>
#include<sys/socket.h>
函数原型:int socket(int domain,int type,int protocol)
domain :协议类型,一般为AF_INET
type:socket类型
protocol:用来指定socket所使用的的协议编号,通常设置为0即可
(2)bind:将socket与本机上的一个端口绑定,之后就可以在端口监听服务请求
#include <sys/types.h>
#include<sys/socket.h>
函数原型:int bind (int sockfd,struct sockaddr*my_addr,int addr_len)
sockfd :socket创建返回值,或者叫描述符
my_addr:是一个指向包含有本机地址IP和端口号等信息的sockaddr类型的指针
addr_len:常被设置为sizeof(struct sockaddr)

(3)connect :面向连接的客户程序使用connect函数来配置socket,并与远端服务器建立TCP连接
#include <sys/types.h>
#include<sys/socket.h>
函数原型:int connect(int sockfd,struct sockaddr*serv_addr,int add_len)
sockfd :socket创建返回值,或者叫描述符
serv_addr:包含目的机器的地址和IP的端口的sockaddr类型指针
addr_len:常被设置为sizeof(struct sockaddr)
(4)listen :将socket处于被动监听状态,并为该socket建立一个输入数据队列,将到达的服务器请求保存在此队列中,直到程序处理他们
#include<sys/socket.h>
函数原型 int listen(int sockfd,int backlog)
sockfd :socket创建返回值,或者叫描述符
backlog: 指定在请求队列中的最大请求数,进入的连接请求将在队列中等待accept()它们。
(5)accept :让服务器接收客户端的连接请求
#include <sys/types.h>
#inlcude <sys/socket.h>
函数原型: int accept(int sockfd, void *addr, int addrlen)
sockfd:是被监听的socket描述符
addr:通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息
addrlen:sizeof(struct sockaddr_in)
(6)close :停止在socket上进行数据传输操作
(7)send:
(8)recv:

C语言实现TCP连接的服务端与客户端程序

程序调试过程中遇到的问题

  1. error C1083: 无法打开包括文件: “sys/socket.h”:,即VS无法找到socket 头文件
    原因:Windows系统下没有这个头文件,
    解决方法:改用<windows.h>,<winsock2.h>
  2. error C2011: “sockaddr”:“struct”类型重定义
    原因:有2种情况(1)有变量名相同
    (2)出现在头文件中,有头文件中类型重复定义
    解决方法:增加预编译语句。 具体方法将#include <windows.h>放在#include<winsock2.h>之后即可。
  3. 问题:关闭socket时没有close函数
    可能在头文件中定义成了closesocket(Socket)
  4. error LNK2019: 无法解析的外部符号 __imp__accept@12,该符号在函数 _main 中被引用
    在界面中可以找到看到定义的函数,但是编译却出现错误,提示无法解析符号
    解决方法,在程序中加入#pragma comment(lib,“Ws2_32.lib”)
  5. socket 创建一直失败,返回值一直是-1
    在Windows调试中,出现返回值总为-1的情况需要加几行代码,在Windows中使用socket需要先注册,注册代码如下:
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if(err != 0)
    {
    perror(“WSAStartup error”);
    }
  6. 连接时,connect失败,可能是两电脑IP不在同一个域

程序

1.客户端
/ TCP_test_client.cpp : 定义控制台应用程序的入口点。169
//

#include “stdafx.h”
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#pragma comment(lib,“ws2_32.lib”)

//定义TCP参数
#define CLIENT_PORT 8080
#define SERVER_ADDR “192.168.5.2”

int main(int )
{
//进行Windows使用socket前的注册
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1,1);
err = WSAStartup(wVersionRequested,&wsaData);
if(err!=0)
{
perror(“WSAStartup error”);
}

SOCKET clientSocket;
struct sockaddr_in serverAddr;
int dataNum;
char sendbuf[200] = {0};

clientSocket = socket(AF_INET,SOCK_STREAM,0);
if(clientSocket < 0)
{
	printf("socket creat failed!\n");

}
else
{
	printf("socket creat success!\n");
}

//初始化
serverAddr.sin_family = AF_INET;
serverAddr.sin_port= htons(CLIENT_PORT);
serverAddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);//指定服务器的IP

SOCKET connectfd ;
while(1)
{
	connectfd = connect(clientSocket,(struct sockaddr * )&serverAddr,sizeof(serverAddr));

if(connectfd < 0)
{
	printf("connect failed\n");
	printf("connectfd = %d\n",connectfd);
}
else
{
	printf("connect success!\n");
}

dataNum = send(clientSocket,sendbuf,sizeof(sendbuf),0);
printf("%d\n",dataNum);
if(dataNum < 0)
{
	printf("data send error !\n");
}

}

getchar();
return 0;

}

2. 服务端

// stdafx.cpp : 只包括标准包含文件的源文件
// TCP_test_sever.pch 将作为预编译头
// stdafx.obj 将包含预编译类型信息

#include “stdafx.h”

// TODO: 在 STDAFX.H 中
// 引用任何所需的附加头文件,而不是在此文件中引用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>

#pragma comment(lib,“ws2_32.lib”)

#define SERVER_PORT 8080
#define SERVER_ADDR “192.168.1.133”
#define PACKAGE_SIZE 1024

int main()
{

//先注册
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1,1);
err = WSAStartup(wVersionRequested,&wsaData);
if(err!=0)
{
	perror("WSAStartup error!");
}

//变量定义
SOCKET serverSocket;
SOCKET client;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buffer[200];
int dataNum = 0;
int addr_len = sizeof(client_addr);
int listenfd = 0;
//socket创建
serverSocket = socket(AF_INET,SOCK_STREAM,6);
if(serverSocket < 0)
{
	printf("socket creat failed !\n");
	//printf("%d",serverSocket);
}
else
{
	printf("socket creat success!\n");
}
//服务信息
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//ip可以是本服务器的IP,也可以用宏INADDR_ANY代替,代表0.0.0.0,表明所有地址

//socket绑定本地
int bindfd = 0;
bindfd = bind(serverSocket,(struct sockaddr *)&server_addr,sizeof(server_addr));
if(bind < 0)
{
	printf("socket  bind failed !\n");
}
else
{
	printf("socket bind success!\n");
}
//监听状态

listenfd = listen(serverSocket,2);
if(listen < 0)
{
	printf("listen error!\n");

}
printf("%d\n",listenfd);

printf("监听端口: %d\n",SERVER_PORT);
int  asc = 200;
printf("4897\n");

while(1)

{
	client = accept(serverSocket,(struct sockaddr*)&client_addr,(socklen_t *)&addr_len);
	printf("1\n");
	if(client < 0)
	{
		printf("accept error\n");
	}
	else
	{
		printf("accept  success\n");
	}
	asc = 200;
	while(asc)
	{
		//printf("1\n");
		dataNum = recv(client,buffer,200,0);
		//printf("2\n");
		if(dataNum < 0)
		{
			printf("data  recv error!\n");
		}
		printf("%d\n",dataNum);
		asc -= dataNum;
		//closesocket(client);
	}
	//closesocket(client);
	printf("852\n");	

}
closesocket(serverSocket);

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

鱼夹心

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

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

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

打赏作者

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

抵扣说明:

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

余额充值