一个简单的hello/hi的网络聊天程序

TCP套接字函数了解

socket函数

	为了执行网络I/O,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型(使用ipv4的TCP、使用ipv6的UDP、Unix域字节流协议等)

#include<sys/socket.h>
int socket(int family, int type, int protocol);
					返回:若成功则为非负描述符
						 若出错则为-1

	其中family参数指明协议族,type参数之梦套接字类型,protocol参数为某个协议类型常值,或者为0,以选择所给定family和type组合的系统默认值

family 说明
AF_INET Ipv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字

**socket函数的family常值**
   type     	  说明   

SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据包套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字

**socket函数的type常值**
protocol  	   说明   

IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议

**socket函数AF_INET或FF_INET6的protoco常值**
	socket函数在成功时返回一个小的非负整数值,它与文件描述符相似,我们把它称为套接字描述符(socket description),简称sockfd。为了得到整个套接字描述符,我们只是指定了协议族(ipv4、ipv6或unix)和套接字类型(字节流、数据报或原始套接字)

connect函数

TCP客户用connect函数来建立与TCP服务器的连接

#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)
				返回:若成功则为0
					 若出错则为-1

	sockfd是由socket函数返回的套接字描述符,第二个、第三个参数分别是指向套接字地址结构的指针和该结构的大小。套接字地址必须含有服务器的IP地址和口号。

	客户在调用connect函数之前不必非得调用bind函数,因为需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。

bind函数

	bind函数把一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是32位的ipv4地址或128位的ipv6地址与16位的TCP或UDP端口号的组合。

#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen)
    					返回:若成功则为0
      						 若出错则为-1

	第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用bind函数可以指定一个端口号,或指定一个ip地址,也可以两者都指定,还可以都不指定。

listen函数

	listen函数仅由TCP服务器调用,它做两件事
  • 当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向该套接字的连接请求。根据TCP连接转换图,调用listen导致套接字从CLOSED转台转换为LISTEN状态。

  • 本函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数、

    include<sys/socket.h>

    int listen(int sockfd, int backlog)
    返回:若成功则为0
    若出错则为-1

      本函数通常应该在调用socket和bind两个函数之后,并在调用accept函数之前调用。为了更加理解backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列
    
  • 未完成连接队列(incomplete Connection queue),每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程

  • 已完成连接队列(completed Connection queue),每个已完成TCP三次握手过程的客户对应其中一项

accept函数

	accept函数由TCP 服务器调用,用于从已完成连接队列对头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入到睡眠。

#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
					返回:若成功为为非负描述符
						 若出错则为-1

	参数cliaddr和addrlen用来返回已连接的对端进程(客户)的协议地址。addrlen是值-结果参数:调用前,将由*addrlen所引用的整数值置为有cliaddr所指的套接字地址结构的长度,返回时,该整数值即为由内核存放在该套接字地址结构中的确切字节数。

	如果accept函数返回成功,那么返回值是由内核自动生成的一个全新描述符,代表与所返回的TCP连接。其第一个参数为监听套接字(listening socket)描述符(由socket创建,随后用作bind和listen的第一个参数的描述符),它的返回值为已连接套接字(connected socket)描述符。

	区分监听套接字(listening socket)描述符和已连接套接字(connected socket)描述符:
  • 一个服务器通常仅仅是创建一个监听套接字,他在该服务器的声明周期内一直存在。
  • 内核为每个服由务器进程接受的客户连接创建一个已连接套接字(说明TCP三次握手已经完成)。当服务器完成对某个给定客户机的服务时,相应的已连接套接字就被关闭

close函数

	通常的unix close 函数也用来关闭套接字,并终止TCP连接

#include<unistd.h>
int close(int sockfd);
					返回:若成功则为0
						 若出错则为-1

	close一个TCP套接字的默认行为是把该套接字标记为已关闭,然后立即返回到调用进程。该套接字描述符不能再由调用进程使用,也就是说它不能再做为read或write的第一个参数,然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列
下图为基于TCP客户/服务器程序的套接字函数及流程 ![](https://img2018.cnblogs.com/blog/1624185/201912/1624185-20191211201717822-1810418049.png)

服务端代码:server.c

//server.cpp
#include< iostream>
#include< winsock2.h>
#include< WS2tcpip.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
constexpr auto serverIP = "127.0.0.1";//服务器端的IP地址,这里设为本机回环地址;
constexpr auto serverPort = 6789;//服务器端进程的端口号,值大于1023即可;
int main() {
	//第一步:WinSock初始化
	WORD wVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	//WSAStartup,Windows Sockets Asynchronous,Windows异步套接字的启动命令。
	if (WSAStartup(wVersion, &wsaData)) {
		cout << "WSAStartup......\n";
		return 0;
	}
	//创建socket
	SOCKET sockServer;
	sockServer = socket(AF_INET, SOCK_STREAM, 0);
	//服务器地址
	SOCKADDR_IN serverAddr, clientAddr;
	serverAddr.sin_family = AF_INET;
	in_addr dst;
	//点分十进制的IPv4地址转换为网络字节的IP(整型)
	int res = inet_pton(AF_INET, serverIP, (void*)&dst);
	//将serverIP地址转换为in_addr的结构体,并复制在dst中
	if (res == 1) {
		serverAddr.sin_addr.S_un.S_addr = dst.s_addr;
	}
	else {
		cout << "The address is error." << endl;
	}
	serverAddr.sin_port = htons(serverPort);//host to network short 
											//绑定
	bind(sockServer, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR));
	//监听
	listen(sockServer, 10);//10(服务器最多可以处理的用户数)
						   //处理
	int acceptResult = sizeof(SOCKADDR);
	SOCKET sockAccept;
	cout << "The server is waiting for client's connection request..." << endl;
	sockAccept = accept(sockServer, (SOCKADDR*)&clientAddr, &acceptResult);
	if (sockAccept == INVALID_SOCKET) {
		cout << "The server failed to accept the client's connection request." << endl;
		return 0;
	}
	else {
		cout << "The server accepted the client' connection request." << endl;
	}
	//cout <<clientAddr.sin_addr .S_un.S_addr<< endl;
	//cout << clientAddr.sin_port;
	//传送、接收数据
	char sendbuffer[256] = { '\0' };
	char recivebuffer[256] = { '\0' };
	for (;;) {
		//接收数据
		recv(sockAccept, recivebuffer, 256, 0);
		if (strcmp(recivebuffer, "quit") == 0) {
			cout << "client quit." << endl;
			break;
		}
		else {
			cout << "Client says:>" << recivebuffer << endl;
			cout << "Server says:>";
			cin >> sendbuffer;
			send(sockAccept, sendbuffer, strlen(sendbuffer) + 1, 0);
		}
	}
	closesocket(sockServer);
	WSACleanup();
	return 0;
}

客户端代码:client.c

#include< iostream>
#include< winsock2.h>
#include< ws2tcpip.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
constexpr auto serverIP = "127.0.0.1";//服务器端的IP地址;
constexpr auto serverPort = 6789;//服务器端进程的端口号;
int main() {
	//WinSock初始化
	WORD wVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	//WSAStartup,Windows Sockets Asynchronous,Windows异步套接字的启动命令。
	if (WSAStartup(wVersion, &wsaData)) {
		cout << "WSAStartup......" << endl;
		return 0;
	}
	//创建套接字
	SOCKET sockClient;
	sockClient = socket(AF_INET, SOCK_STREAM, 0);//1.address family,地址族协议,IPv4
												 //2.流式套接字
												 //3.已使用TCP,默认0
												 //服务器地址
	SOCKADDR_IN  serverAddr;
	serverAddr.sin_family = AF_INET;
	in_addr dst;
	//点分十进制的IPv4地址转换为网络字节的IP(整型)
	int IPResult = inet_pton(AF_INET, serverIP, &dst);

	//将serverIP地址转换为in_addr的结构体,并复制在dst中
	if (IPResult == 1) {
		serverAddr.sin_addr.S_un.S_addr = dst.s_addr;
	}
	else {
		cout << "The IP is error." << endl;
	}
	serverAddr.sin_port = htons(serverPort);
	//请求连接
	int connectResult = connect(sockClient, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR));
	if (connectResult != 0) {
		cout << "The client's connection request failed." << endl;
	}
	else {
		cout << "The client's connection request is accepted." << endl;
	}
	//发送及接收数据
	char sendbuffer[256] = { '\0' };
	char receivebuffer[256] = { '\0' };
	for (;;) {
		//发送数据
		cout << "The client says:>";
		cin >> sendbuffer;
		if (strcmp(sendbuffer, "quit") == 0) {
			break;
		}
		else {
			send(sockClient, sendbuffer, strlen(sendbuffer) + 1, 0);
		}
		//接收数据
		recv(sockClient, receivebuffer, 256, 0);
		cout << "The server says:>" << receivebuffer << endl;
	}
	closesocket(sockClient);//关闭套接字
	WSACleanup();//注销及释放分配资源
	return 0;
}

运行截图

posted @ 2019-12-11 20:18  KTT飞  阅读(209)  评论(0编辑  收藏  举报