Linux 网络编程——TCP编程

概述

TCP(Transmission
Control Protocol 传输控制协议)
是一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP 具有以下特点:

1)电话系统服务模式的抽象

2)每一次完整的数据传输都要经过建立连接、使用连接、终止连接的过程

3)可靠、出错重传、且每收到一个数据都要给出相应的确认,保证数据传输的可靠性

TCP 编程的 C/S 架构

基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下:

TCP 客户端编程

所需头文件:#include <sys/socket.h>

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

功能:

创建一个用于网络通信的 socket套接字(描述符),详细用法,请看《套接字的介绍》

参数:

family:本示例写 AF_INET,代表 IPv4

type:本示例写 SOCK_STREAM,代表 TCP 数据流

protocol:这里写 0,设为 0 表示使用默认协议

返回值:

成功:套接字

失败 < 0

int connect( int sockfd,

const struct sockaddr *addr,

socklen_t len );

功能:

主动跟服务器建立连接,有点类似于,我们给别人电话,主动拨对方的电话号码,具体是怎么一个过程,请《connect()、listen()和accept()三者之间的关系》

参数:

sockfd:socket()返回的套接字

addr:连接的服务器地址结构

len:地址结构体长度

返回值:

成功:0

失败:-1

connect() 函数相当于拨号码,只有拨通号码并且确定对方是自己要找的人(三次握手)才能进行下一步的通信。

ssize_t send(int sockfd,

const void* buf,

size_t nbytes,

int flags);

功能:

发送数据,最后一个参数为 0 时,可以用 write() 替代( send 等同于 write )。注意:不能用 TCP 协议发送 0 长度的数据包。假如,数据没有发送成功,内核会自动重发。

参数:

sockfd: 已建立连接的套接字

buf: 发送数据的地址

nbytes: 发送缓数据的大小(以字节为单位)

flags: 套接字标志(常为 0)

返回值:

成功:成功发送的字节数

失败 < 0

这里通过 Windows 的网络调试助手和虚拟机中的 ubuntu 客户端程序进行通信,网络调试助手下载请点此处

Windows 的网络调试助手作为 TCP 服务器,接收客户端的请求,调试助手配置如下:

对于 TCP 客户端编程流程,有点类似于打电话过程:找个可以通话的手机(
socket() ) -> 拨通对方号码并确定对方是自己要找的人(
connect() ) -> 主动聊天(
send() 或 write() )
-> 或者,接收对方的回话(
recv() 或 read() )
-> 通信结束后,双方说再见挂电话( close() )。

虚拟机中 ubuntu 的 TCP 客户端程序代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
	unsigned short port = 8080;        		// 服务器的端口号
	char *server_ip = "10.221.20.10";    	// 服务器ip地址

	if( argc > 1 )		//函数传参,可以更改服务器的ip地址
	{
		server_ip = argv[1];
	}
	if( argc > 2 )	   //函数传参,可以更改服务器的端口号
	{
		port = atoi(argv[2]);
	}

	int sockfd;
	sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字
	if(sockfd < 0)
	{
		perror("socket");
		exit(-1);
	}

	// 设置服务器地址结构体
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址
	server_addr.sin_family = AF_INET;	// IPv4
	server_addr.sin_port = htons(port);	// 端口
	inet_pton(AF_INET, server_ip, &server_addr.sin_addr);	// ip

	 // 主动连接服务器
	int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(err_log != 0)
	{
		perror("connect");
		close(sockfd);
		exit(-1);
	}

	char send_buf[512] = {0};
	printf("send data to %s:%d\n",server_ip,port);
	while(1)
	{
		printf("send:");
		fgets(send_buf,sizeof(send_buf),stdin); // 输入内容
		send_buf[strlen(send_buf)-1]='\0';
		send(sockfd, send_buf, strlen(send_buf), 0);   // 向服务器发送信息
	}

	close(sockfd);

	return 0;
}

运行结果如下:

对于客户端,也是可以接收数据,前提为,客户端先给服务器发送数据。

ssize_t recv(int sockfd,

void *buf,

size_t nbytes,

int flags);

功能:

接收网络数据,默认的情况下,如果没有接收到数据,这个函数会阻塞,直到有数据到来。

参数:

sockfd:套接字

buf:
接收网络数据的缓冲区的地址

nbytes:接收缓冲区的大小(以字节为单位)

flags:
套接字标志(常为 0 )

返回值:

成功:成功接收的字节数

失败 < 0

测试代码如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
	unsigned short port = 8080;        		// 服务器的端口号
	char *server_ip = "10.221.20.10";    	// 服务器ip地址

	if( argc > 1 )		//函数传参,可以更改服务器的ip地址
	{
		server_ip = argv[1];
	}
	if( argc > 2 )	   //函数传参,可以更改服务器的端口号
	{
		port = atoi(argv[2]);
	}

	int sockfd;
	sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字
	if(sockfd < 0)
	{
		perror("socket");
		exit(-1);
	}

	// 设置服务器地址结构体
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址
	server_addr.sin_family = AF_INET;	// IPv4
	server_addr.sin_port = htons(port);	// 端口
	inet_pton(AF_INET, server_ip, &server_addr.sin_addr);	// ip

	 // 主动连接服务器
	int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(err_log != 0)
	{
		perror("connect");
		close(sockfd);
		exit(-1);
	}

	printf("send data to %s:%d\n",server_ip,port);

	char send_buf[512] = "Hi, I am Mike.";
	send(sockfd, send_buf, strlen(send_buf), 0);   // 向服务器发送信息

	char recv_buf[512] = {0};
	recv(sockfd, recv_buf, sizeof(send_buf), 0); // 接收数据
	printf("recv_buf ========== %s\n", recv_buf);

	close(sockfd);

	return 0;
}

运行结果如下:

TCP 服务器编程

做为 TCP 服务器需要具备的条件呢?

  • 具备一个可以确知的地址( bind() ):相当于我们要明确知道移动客服的号码,才能给他们电话;
  • 让操作系统知道是一个服务器,而不是客户端( listen() ):相当于移动的客服,他们主要的职责是被动接听用户电话,而不是主动打电话骚扰用户;
  • 等待连接的到来( accept() ):移动客服时刻等待着,来一个客户接听一个。

接收端使用 bind() 函数,来完成地址结构与
socket 套接字的绑定,这样 ip、port 就固定了,发送端即可发送数据给有明确地址( ip+port ) 的接收端。

int bind( int sockfd,

const struct sockaddr *myaddr,

socklen_t addrlen );

功能:

将本地协议地址与 sockfd 绑定,这样 ip、port 就固定了

参数:

sockfd:socket
套接字

myaddr: 指向特定协议的地址结构指针

addrlen:该地址结构的长度

返回值:

成功:返回 0

失败:-1

使用实例如下:

// 本地网络地址
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));	// 清空结构体内容
my_addr.sin_family = AF_INET;	// ipv4
my_addr.sin_port   = htons(port);	// 端口转换
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定网卡所有ip地址,INADDR_ANY为通配地址,值为0

printf("Binding server to port %d\n", port);
int err_log;
err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); // 绑定
if(err_log != 0)
{
	perror("bind");
	close(sockfd);
	exit(-1);
}

int listen(int sockfd, int backlog);

功能:

将套接字由主动修改为被动,使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接。更详细说明,请看《connect()、listen()和accept()三者的关系》

参数:

sockfd: socket监听套接字

backlog:连接队列的长度

返回值:

成功:返回0

失败:其他

int accept(  int sockfd,

struct sockaddr *cliaddr,

socklen_t *addrlen );

功能:

从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞)。更详细说明,请看《connect()、listen()和accept()三者的关系》

参数:

sockfd: socket监听套接字

cliaddr: 用于存放客户端套接字地址结构

addrlen:套接字地址结构体长度的地址

返回值:

成功:已连接套接字。注意:返回的是一个已连接套接字,这个套接字代表当前这个连接

失败:< 0

对于
TCP 服务器编程流程,有点类似于接电话过程
:找个可以通话的手机(socket() ) -> 插上电话卡固定一个号码(
bind() ) -> 职责为被动接听,给手机设置一个铃声来监听是否有来电( listen() ) -> 有来电,确定双方的关系后,才真正接通不挂电话( accept() ) -> 接听对方的诉说(
recv() ) -> 适当给些回话(
send() )-> 通信结束后,双方说再见挂电话( close() )。

ubuntu 中的服务器代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
	unsigned short port = 8080;	// 本地端口
	if(argc > 1)
	{
		port = atoi(argv[1]);
	}

	int sockfd = socket(AF_INET, SOCK_STREAM, 0);   // 创建通信端点:套接字
	if(sockfd < 0)
	{
		perror("socket");
		exit(-1);
	}

	// 设置本地地址结构体
	struct sockaddr_in my_addr;
	bzero(&my_addr, sizeof(my_addr));	// 清空
	my_addr.sin_family = AF_INET;	// ipv4
	my_addr.sin_port   = htons(port);	// 端口
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);	// ip

	// 绑定
	int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
	if( err_log != 0)
	{
		perror("binding");
		close(sockfd);
		exit(-1);
	}

	err_log = listen(sockfd, 10); // 监听,监听套接字改为被动
	if(err_log != 0)
	{
		perror("listen");
		close(sockfd);
		exit(-1);
	}	

	printf("listen client @port=%d...\n",port);

	while(1)
	{	

		struct sockaddr_in client_addr;
		char cli_ip[INET_ADDRSTRLEN] = "";
		socklen_t cliaddr_len = sizeof(client_addr);    

		int connfd;
		// 等待连接
		connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
		if(connfd < 0)
		{
			perror("accept");
			continue;
		}

		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
		printf("----------------------------------------------\n");
		printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));

		char recv_buf[512] = "";
		while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 ) // 接收数据
		{
			printf("\nrecv data:\n");
			printf("%s\n",recv_buf);
		}

		close(connfd);     //关闭已连接套接字
		printf("client closed!\n");
	}

	close(sockfd);         //关闭监听套接字

	return 0;
}

Windows 的网络调试助手作为 TCP 客户端,给 ubuntu 中的服务器发送数据,运行结果如下:

关闭连接:close()

使用 close() 函数即可关闭套接字,关闭一个代表已连接套接字将导致另一端接收到一个
0 长度的数据包,详情请看《 TCP 四次挥手》

做服务器时

  • 关闭监听套接字( socket()和listen()之后的套接字 )将导致服务器无法接收新的连接,但不会影响已经建立的连接;
  • 关闭 accept()返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器的监听( socket()和listen()之后的套接字 )。

做客户端时

关闭连接就是关闭连接,不意味着其他。

通常的情况下,是先关闭客户端,再关闭服务器,如果是先关闭服务器,立马启动服务器是,服务器绑定的端口不会立马释放(如下图),要过 1 分钟左右才会释放,为什么会这样的呢?请看《
TCP 四次挥手》
。有没有方法让服务器每次启动都能立即成功?请看《端口复用》

时间: 2024-10-13 11:37:52

Linux 网络编程——TCP编程的相关文章

五十四、linux 编程——TCP 编程模型

54.1 编程模型介绍 54.1.1 TCP 客户端服务器编程模型 客户端调用序列 调用 socket 函数创建套接字 调用 connect 连接服务器端 调用 I/O 函数(read/write) 与服务器端通讯 调用 close 关闭套接字 服务器端调用序列 调用 socket 函数创建套接字 调用 bind 绑定本地地址和端口 调用 listen 启动监听 调用 accept 从已连接队列中提取客户连接 调用 I/O 函数(read/write)与客户端通讯 调用 close 关闭套接字

Linux网络之socket编程 (1)

在谈到socket编程之前,首先我们要知道一点预备知识. 预备知识: 1.网路字节序全部采用大端字节序. 关于字节序的详解,戳链接 查看,这里不做解释. 2.在编程之前,我们有必要了解,什么是socket? socket,又叫做套接字.我们都应该知道,在网络中,IP地址+ 端口号,可以唯一表示互联网中的一个进程,因此,我们将  IP地址+端口号 称为socket. socket API是一套抽象的网络编程接口,适用于各种底层网络协议,包括IPv4,IPv6以及UNIX Domain Socket

五十四 网络编程 TCP编程

Socket是网络编程的一个抽象概念.通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可. 客户端 大多数连接都是可靠的TCP连接.创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器. 举个例子,当我们在浏览器中访问新浪时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接.如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了. 所以,我们要

linux下面的tcp编程

首先要建立服务器建立起socket,然后与本地的端口进行绑定,接着就开始接收客户端的请求并建立与它的连接,接下来,客户端发送的消息. tcpserver.c代码: int main() { struct sockaddr_in server_sockaddr,client_sockaddr; int sin_size,recvbytes; int sockfd, client_fd; char buf[BUFFER_SIZE]; /* 建立socket连接 */ if((sockfd = soc

java 网络编程-TCP编程图

原文地址:https://blog.51cto.com/14437184/2432747

五、linux网络和TCP、ip协议

一.网络配置 linux 的网络是属于内核的,所以可以在linux主机上设置多个网段的ip地址,在ping操作的时候都会响应. 1.网络接口类型: lo:本地回环 以太网网卡:ethx centos6.0以上版本修改以太网卡名称: [[email protected] ~]# more /etc/udev/rules.d/70-persistent-net.rules # This file was automatically generated by the /lib/udev/write_n

63 网络编程(四)——TCP编程

TCP编程 TCP编程是面向连接的数据传输,所以需要时用IO流来建立连接. 用户输出流到服务器,服务器输入流接收数据. 服务器输出流到用户,用户输入流接收. 基本流程 服务器端 创建服务器端:ServerScoekt 对象 阻塞时监听用户接入:accep() 返回Socket对象 建立连接:与返回的Socket对象建立IO流,getInputStream()方法与getOutputStream方法 处理数据 关闭流,关闭Socket对象,关闭服务器(一般不关服务器) 用户端 创建Socket对象

linux网络编程笔记——TCP

1.TCP和UDP TCP是长连接像持续的打电话,UDP是短消息更像是发短信.TCP需要消耗相对较多的资源,但是传输质量有保障,UDP本身是不会考虑传输质量的问题. 2.网络传输内容 我习惯的做法是直接通过TCP传送结构体,当然前提是收发两端都在程序里对目标结构体有充分的定义.特别说明的一点是,要小心收发两端处理器的大小端问题!而且传输信息头里必须包含长度信息,而且通用的是大端.但是,这里的长度和结构体,我选择用小端进行传输. 3.TCPserver实现 参考了别人多线程的回调写法,看起来不错.

Linux网络编程——tcp并发服务器(poll实现)

想详细彻底地了解poll或看懂下面的代码请参考<Linux网络编程--I/O复用之poll函数> 代码: #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.h> #include <sys/socket.h> #incl