网络编程概述
本文将介绍网络编程中的一些基础知识,包括TCP和UDP的区别、端口号的作用、字节序的概念,以及通过socket编程实现客户端和服务器之间的通信。我们还会展示一些示例代码,帮助理解这些概念的实际应用。
1. TCP/UDP的区别,端口号的作用
1.1 TCP/UDP的区别
在TCP/IP 协议中, TCP 协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送
syn 包 (tcp 协议中 syn 位置 1 ,序号为 J) 到服务器,并进
入 SYN_SEND 状态,等待服务器确认;
第
二次握手:服务器收到 syn 包,必须确认客户的 SYN ,同时自己也发送一个 SYN 包,即 SYN+ACK
包( tcp 协议中 syn 位置 1 ack 位置 1 ,序号 K ,确定序号为 J+1 ),此时服务器进入 SYN_RECV 状
态;
第三次握手:客户端收到服务器的
SYN ACK 包,向服务器发送确认包 ACK(tcp 协议中 ack 位置
1 ,确认序号 K+1) 1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。
通过这样的
三次握手 ,客户端与服务端建立起可靠的双工的连接,开始传送数据。三次握手的最主要目
的是保证连接是双工的,可靠更多的是通过重传机制来保证的。
但是为什么一定要进行三次握手来保证连接是双工的呢,一次不行么?两次不行么?
我们举一个现实生活中两个人进行语言沟通的例子来模拟
三次握手 。 同理对于 TCP 为什么需要进行三
次握手我们可以一样的理解:为了保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次
第一次和第二次 握手,为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次 第二
次和第三次 握手。
四次挥手
由于
TCP 连接是全双工的,因此每个方向都必须单独进行关闭。这好比,我们打电话(全双工),正
常的情况下(出于礼貌),通话的双方都要说再见后才能挂电话,保证通信双方都把话说完了才挂电话。
TCP(传输控制协议) 和 UDP(用户数据报协议) 是互联网协议套件中的两个核心传输层协议。它们之间的主要区别如下:
-
连接方式:
- TCP是面向连接的(如打电话要先拨号建立连接),需要在传输数据之前建立连接。
- UDP是无连接的,即发送数据之前,不需要建立连接。
-
可靠性:
- TCP提供可靠的服务,保证数据无差错、不丢失、不重复且按序到达。
- UDP尽最大努力交付,不保证可靠交付。
-
数据传输:
- TCP面向字节流,将数据视为一连串无结构的字节流。
- UDP面向报文,支持面向报文的传输方式。
-
通信方式:
- 每一条TCP连接只能是点到点的。
- UDP支持一对一、一对多、多对一和多对多的交互通信。
-
首部开销:
- TCP首部开销较大,为20字节。
- UDP首部开销小,只有8字节。
-
信道类型:
- TCP的逻辑通信信道是全双工的可靠信道。
- UDP则是不可靠信道。
1.2 端口的作用
IP地址标识网络中的主机,但一台主机可以提供多个网络服务(如Web服务、FTP服务等)。为了区分不同的服务,使用“IP地址+端口号”来标识具体的服务。
- 端口号 提供了访问通道。服务器通常通过知名端口号来识别。例如,FTP服务器的TCP端口号为21,Telnet服务器的TCP端口号为23,TFTP服务器的UDP端口号为69。
2. 字节序
- 字节序 指的是多字节数据在内存中的存储顺序。常见的字节序有两种:大端字节序(Big-endian)和小端字节序(Little-endian)。
- x86系列CPU通常采用小端字节序(Little-endian)。
- 网络字节序通常采用大端字节序(Big-endian)。
3. Socket编程实现双方消息发送
3.1 Socket服务器实现不断接收客户端信息
通过Socket编程实现一个简单的服务器不断接收客户端信息,并处理数据。以下是一个简单的Socket服务器和客户端示例代码。
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
char *msg = "I got your message";
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr, 0, sizeof(struct sockaddr_in));
memset(&c_addr, 0, sizeof(struct sockaddr_in));
if (argc != 3) {
printf("param isn't right\n");
return -1;
}
// 1. 创建socket
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if (s_fd == -1) {
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1], &s_addr.sin_addr);
// 2. 绑定地址
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
// 3. 监听端口
listen(s_fd, 10);
// 4. 接受客户端连接
int clen = sizeof(struct sockaddr_in);
while (1) {
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
if (c_fd == -1) {
perror("accept");
continue;
}
printf("get connect: %s\n", inet_ntoa(c_addr.sin_addr));
if (fork() == 0) {
// 5. 读取客户端数据
n_read = read(c_fd, readBuf, 128);
if (n_read == -1) {
perro