TCP的服务器和客户端的通信

1.网络编程的目标

实现不同主机之间的进程间通信(服务器和客户端的通信),并且一个服务器可以处理很多个客户端的请求。

2.OSI模型与TCP/IP协议体系结构

协议:通信双方所遵守的通信规则。

网络体系结构:指网络的层次结构和每层所使用的协议的集合。

网络采用分而治之的方法设计,将网络的功能划分为不同的模块、以分层的形式有机组合在一起。每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务。

两类非常重要的体系结构:OSI与TCP/IP

OSI与TCP/IP参考模型的对应关系

                                               OSI模型                       TCP/IP协议

 3.UDP和TCP协议

共同点:同为传输层协议

不同点:TCP:有连接,可靠

              UDP:无连接,不保证可靠

有连接:正式数据传输之前确保双方都能收和发

无连接:直接传输数据,不管是否收和发

 可靠:即数据无误、数据无丢失、数据无失序、数据无重复到达的通信。

不保证可靠:有可能会丢失、会出错、会失序或者重复到达。

TCP有连接:

三次握手:

第一次握手:客户端给服务器发送连接请求(客户端能发)

第二次握手:服务器给客户端应答同时给客户端发送连接请求(服务器能收,服务器能发)

第三次握手:客户端给服务器应答(客户端能收)

四次挥手:

第一次挥手:客户端向服务器发送断开连接请求(客户端不发了)

第二次挥手:服务器应答(服务器不收了)

第三次挥手:服务器向客户端发送断开连接请求(服务器不发了)

第四次挥手:客户端应答(客户端不收了)

适用场合:TCP适合于对传输质量要求较高,以及传输大量数据的通信,比如登录、文件的传输等

UCP适合于对传输速度要求较高,以及小尺寸数据的通信,比如视频会议等

4.预备知识

1、socket

 

 socket类型:1、流式套接字(SOCK_STREAM):提供了一个面向连接、可靠的数据传输服务、数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方,数据被看作是字节流,无长度限制。

2、数据报套接字(SOCK_DGRAM):提供无连接服务。数据包以独立数据报的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。

3、原始套接字(SOCK_RAW):可以对较低层次协议如IP、ICMP直接访问。

 

2、IP地址:IP地址是Internet中主机的标识

Internet中的主机要与别的机器通信必须具有一个IP地址

IP地址为32位(IPv4)或者128位(IPv6)

每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由。

3、端口号

为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别

4、字节序

大端序:高地址存放低字节,低地址存放高字节

小端序:高地址存放高字节,低地址存放低字节

大小端由CPU决定

网络字节序:大端

A主机发送数据前:先将本地字节序转换成网络字节序

B主机收到数据后:将网络字节序转换成本地字节序

int inet_aton(const char *strptr, struct in_addr *addrptr):将strptr所指的字符串转换成32位的网络字节序二进制值

in_addr inet_addr(const char *strptr); 功能同上,返回转换后的地址

char *inet_ntoa(stuct in_addr inaddr);将32位网络字节序二进制地址转换成点分十进制的字符串

5、TCP服务器/客户端

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

domain是地址族

PF_INET // Internet协议

PF_UNIX // unix internal协议

PF_NS //Xerox NS协议

PF_IMPLINK // Internet Message 协议

type // 套接字类型

SOCK_STREAM // 流式套接字

SOCK_DGRAM // 数据报套接字

SOCK_RAW // 原始套接字

protocol参数通常置为0

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

sockfd:socket调用返回的文件描述符

struct sockaddr_in      // Internet协议地址结构

        u_short sin_family; // 地址族

        u_short sin port; // 端口

        struct in_addr sin_addr;// IPV4地址

        char sin_zero[8];// 8bytes unused,作为填充

  };

addrlen:sockaddr地址结构的长度

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

int listen(int sockfd, int backlog);

sockfd:监听套接字的文件描述符

backlog:指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求

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

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:监听套接字的文件描述符

addr:对方地址

addrlen:地址长度

返回值:成功:已建立好连接的套接字的文件描述符  失败-1

监听套接字:等待被连接的套接字

连接套接字:客户端和服务器三次握手之后才有的套接字,用来和客户端进行数据传输

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:连接套接字的文件描述符

buf:发送缓冲区的首地址

len:发送的字节数

flag:接收方式(通常为0)0表示阻塞接收

返回值:成功:实际接收的字节数  失败:-1,并设置errno

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket返回的文件描述符

serv_addr:服务器的地址信息

addrlen:serv_addr 的长度

返回值:成功0   失败-1

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd:连接套接字的文件描述符

buf:发送缓冲区的首地址

len:发送的字节数

flag:接收方式(通常为0)0表示阻塞接收

返回值:成功:实际接收的字节数  失败:-1,并设置errno

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


int main()
{
	//监听套接字文件描述符
	int listenFd = -1;
	//连接套接字的文件描述符
	int connFd = -1;
	//服务器的地址结构
	struct sockaddr_in servAddr = {0};
	//客户端的地址结构
	struct sockaddr_in cliAddr = {0};
	socklen_t len = sizeof(cliAddr);
	int ret;
	char buf[100] = {0};

	//创建套接字
	listenFd = socket(PF_INET, SOCK_STREAM, 0);
	if(listenFd < 0)
	{
		perror("socket error!\n");
		return -1;
	}
	printf("socket ok!\n");
	//绑定ip地址和端口号
	servAddr.sin_family = PF_INET;
	servAddr.sin_port = htons(10086);
	servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	ret = bind(listenFd, (struct sockaddr *)&servAddr, sizeof(servAddr));
	if(ret < 0)
	{
		perror("bind error!");
		close(listenFd);
		return -1;
	}
	printf("bind ok!\n");
	//创建监听队列(使得主动的套接字变成可以被连接的被动套接字)
	ret = listen(listenFd, 1);
	if(ret < 0)
	{
		perror("listen error");
		close(listenFd);
		return -1;
	}
    printf("listening....\n");
	//等待并建立连接
	connFd = accept(listenFd, (struct sockaddr *)&cliAddr, &len);
	if(connFd < 0)
	{
		perror("accept error!");
		close(listenFd);
		return -1;
	}
	printf("IP:%s,PORT:%d accept ok!\n", inet_ntoa(cliAddr.sin_addr), ntohs(cliAddr.sin_port));
	//接收消息并打印
	ret = recv(connFd, buf, sizeof(buf), 0);
	if(ret < 0)
	{
		perror("recv error!");
		close(listenFd);
		close(connFd);
		return -1;
	}
	printf("recv:%s\n", buf);
	//关闭套接字
	close(listenFd);
	close(connFd);
	return 0;
}
//tcpClient
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>

int main()
{
	int sockFd = -1;
	//服务器的地址结构
	struct sockaddr_in servAddr = {0};
	int ret;
	char buf[100] = "aaaaaaaaa";

	//创建套接字
	sockFd = socket(PF_INET, SOCK_STREAM, 0);
	if(sockFd < 0)
	{
		perror("socket error!\n");
		return -1;
	}
	printf("socket ok!\n");
	//按照服务器的ip地址和端口号连接服务器
	servAddr.sin_family = PF_INET;
	servAddr.sin_port = htons(10086);
	servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	ret = connect(sockFd, (struct sockaddr *)&servAddr, sizeof(servAddr));
	if(ret < 0)
	{
		perror("connect error!");
		close(sockFd);
		return -1;
	}
	printf("connect ok!\n");
	//发送消息
    ret = send(sockFd, buf, sizeof(buf), 0);
	if(ret < 0)
	{
		perror("send error!");
		close(sockFd);
		return -1;
	}
	printf("send ok!\n");
	//关闭套接字
	close(sockFd);
	return 0;
}


版权声明:本文为weixin_57539783原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。