《TCP/IP网络编程》第2章 笔记&代码&注释

注:本章内容大部分为第一章的服务器与客户端代码的解析与理解,十分重要

协议

服务器端和客户端为了能进行数据交换,他们必须遵循同一个协议:

 创建套接字

int socket(int domain, int type, int protocol)//Linux
Socket socket(int domain, int type, int protocol)//Win

domain 参数为协议族(Protocol Family)信息

type 为套接字数据传输类型信息

protocol 为计算机通信中使用的协议信息

如:

hServerSock = socket(PF_INET, SOCK_STREAM, 0);

代码来自第一章内容中套接字的创建,其中,PF_INET,为IPv4互联网协议族。

协议族(Protocol Family)

Linux中,头文件sys/socket.h中声明的协议族(Windows在<WinSock2.h>中):

PF_INET IPv4协议族
PF_INET6 IPv6协议族
PF_LOCAL 本地通信的UNIX协议族
PF_PACKET 底层套接字的协议族
PF_IPX IPX Novell协议族

业界常用PF_INET对应的IPv4协议族,且《TCP/IP网络编程》主要将重点放在这PF_INET上。

套接字类型(type)

socket()中第二个参数为套接字类型,指的是套接字的数据的传输方式。

决定套接字的传递方式的原因:每一个协议族内会存在不同的传输方式。

两种代表性的传输方式:

1.面向连接的套接字 SOCK_STREAM

hServerSock = socket(PF_INET, SOCK_STREAM, 0);
  • 传输过程数据不会消失
  • 按顺序传输数据
  • 传输的数据不存在数据边界(因为不存在数据边界,所以要注意要在缓冲区(buffer)满之前将数据读出,也就是缓冲区写入速度要小与读出速度)

2.面向消息的套接字 SOCK_DGRAM

  • 强调快速的传输而非传输顺序
  • 传输的数据可能丢失/损毁
  • 有数据边界
  • 限制每次传输的数据大小

这种传输方式与SOCK_STREAM相比突出的是传输的速度这一特性,但他却无法避免数据丢失、损毁

注:面向消息的套接字不存在连接的概念

协议的最终选择(socket()第三个参数)

socket()内前两个参数满足了大部分情况,我们向他传递0,但如果协议族中存在多个 数据方式传输方式相同的协议时,传输方式相同,但协议不同,这时就需要第三个参数具体指定协议信息。

int tcp_sock= socket(PF_INET, SOCK_STREAM, IPPROTO_UDP);//Linux

这里前两个参数指定了IPv4协议族,SOCK_STREAM是面向连接的数据传输:因此满足这两个条件的协议只有IPPROTO_UDP。

基于WINDOWS的TCP套接字示例

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
//这是将静态库连接到工程中
//否则会出现一串无法解析的外部符号的LINK2019的报错
 void ErrorHandling(const char* message);

 int main()
 {
	 WSADATA wsaData;
	 //声明SOCKET变量保存socket()函数的返回值
	 //在Windows里sokect()的返回值是SOCKET
	 SOCKET hSocket;		
	 SOCKADDR_IN servAddr;
	 
	 char message[30];
	 int strLen = 0;
	 int readLen = 0;
	 int idx=0;
	 /*
	 if (argc != 3) {
	 printf("Usage: %s <IP> <port>\n", argv[0]);
	 exit(1);
	 }*/
	 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		 ErrorHandling("WSAStartup() error!");

	 hSocket = socket(PF_INET, SOCK_STREAM, 0);
	 if (hSocket == INVALID_SOCKET)
		 ErrorHandling("socket() error!");

	 memset(&servAddr, 0, sizeof(servAddr));
	 servAddr.sin_family = AF_INET;
	 servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
	 servAddr.sin_port = htons(8888);	
	 
	 if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		 ErrorHandling("connect() error!");
	//while中recv()读取数据,每次1字节
	//第一章中:
	//strlen = recv(hSocket, message, sizeof(message) - 1, 0);
	//每次读取的字符数是整个message的sizeof(message);
	 while (readLen = recv(hSocket, &message[idx++], 1, 0))
	 {
		 if(readLen==-1)
			 ErrorHandling("read() error!");
		 strLen += readLen;
		 if (message[idx - 1] == '\0')//字符串结束符跳出循环
			 break;
	}
	 std::cout << "Message from server : " << message << std::endl;
	 std::cout << "Function read call count : " << strLen << std::endl;

	 closesocket(hSocket);
	 WSACleanup();
	 getchar();
	 return 0;
 }

 void ErrorHandling(const char * message) {
	 fputs(message, stderr);
	 fputc('\n', stderr);
	 exit(1);
 }

注:

#define _WINSOCK_DEPRECATED_NO_WARNINGS

是为了防止安全检查,当然你也可以根据编译器的报错选用他推荐的函数。

在添加了:

#include<WinSock2.h>

之后:
#pragma comment(lib,"ws2_32.lib")

这是将静态库连接到工程中
否则会出现一串无法解析的外部符号的LINK2019的报错

服务器端代码:https://github.com/ChristmasError/TCP-IP-Network-programming/tree/master/%E7%AC%AC%E4%B8%80%E7%AB%A0%20%E5%A5%97%E6%8E%A5%E5%AD%97%E5%92%8C%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/%E6%9C%8D%E5%8A%A1%E5%99%A8

客户端代码:https://github.com/ChristmasError/TCP-IP-Network-programming/tree/master/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E5%A5%97%E6%8E%A5%E5%AD%97%E7%B1%BB%E5%9E%8B%E4%B8%8E%E5%8D%8F%E8%AE%AE%E8%AE%BE%E7%BD%AE

猜你喜欢

转载自blog.csdn.net/qq_43265890/article/details/84727522