tcp网络编程_网络编程tcp - CSDN
精华内容
参与话题
  • TCP网络编程(c)

    2019-12-13 17:08:18
    这不是一个面向0基础人的网络编程教程,更像是一个面向大学生网络编程课程复习的东西. 其他关联文章@丶4ut15m: UDP网络编程(c) 多进程并发服务器(c) 多线程并发服务器(c) IO复用(c) TCP 先明确一点,套接字是最...

    这不是一个面向0基础人的网络编程教程,更像是一个面向大学生网络编程课程复习的东西.

    其他关联文章@丶4ut15m:

    UDP网络编程(c)

    多进程并发服务器(c)

    多线程并发服务器(c)

    IO复用(c)

    TCP

    先明确一点,套接字是最重要的东西. 体会流程.

    服务端

    ->创建套接字(socket)

    ->绑定套接字及地址结构(bind)

    ->监听套接字(listen)

    ->等待连接请求到达并接受(accept)

    ->通过已连接套接字与客户端进行交互(recv,send 或者自定义函数function)

    ->交互完毕,关闭已连接套接字

    ->关闭监听套接字

    PS.recv和send可以和read,write函数互换

    客户端

    ->创建套接字(这里和获取远程服务器信息的顺序可以换,不重要)

    ->获取远程服务器信息(gethostbyname)

    ->配置服务器地址结构

    ->发出连接请求(connect)

    ->服务端接受连接请求;可与服务端进行交互(recv,send或者自定义函数function)

    编程时先把大体框架也即是上面所述给写好,再添细节也不迟(比如地址重用);

    详细内容见我的作业一和代码.

    TCP编程流程

    下面给个实例

    客户端:
    	从命令行读入服务器的IP地址,并连接到服务器;
    	循环从命令行读入一行字符串,并传递给服务器;
    	如果用户输入的是quit,则关闭连接;
    	显示服务端返回的字符串;
    
    	通过使用getsockname和getpeername来获取本地和远程用户的地址信息。
    
    服务端:
    	循环接收客户的连接请求,并显示客户的IP地址和端口号;
    	接收客户传来的字符串,反转后发送给客户;
    	打印出程序运行结果,并结合程序进行分析。
    

    服务端代码如下

    #include <stdio.h>
    #include <stdlib.h>
    #include <netinet/in.h>				//地址结构体所在头文件
    #include <sys/socket.h>				//套接字函数所在头文件
    #include <string.h>				//字符串处理函数所在头文件
    #include <arpa/inet.h>				//inet_ntoa等函数所在头文件
    #include <unistd.h>				//close,read等函数所在头文件
    
    #define PORT 3333
    #define MAXDATASIZE 100
    #define BACKLOG	5
    
    
    //声明函数
    char *reverse(char *str,int len);
    
    int main(){
    	int listenfd,connectfd;			
    	int num;
    	char recvbuf[MAXDATASIZE];
    	struct sockaddr_in server,client;			//服务端和客户端的地址结构
    	socklen_t clientlen;						//客户端地址结构体大小
    	
    
    	//创建套接字.socket
    	if((listenfd = socket(AF_INET,SOCK_STREAM,0)) ==-1){
    		//错误处理
    		perror("Socket error!\n");
    		exit(0);
    	}
    
    	//地址重用,可有可不有,有最好.setsockopt
    	int opt = SO_REUSEADDR;
    	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    
    	//要绑定地址结构便需要配置地址结构
    	//hton的作用是将整型变量从主机字节顺序转变成网络字节顺序(host to net)
    	//如果最后是s就代表short,最后是l则代表是long
    	//也即是htons和htonl
    	memset(&server,'\0',sizeof(server));
    	server.sin_port = htons(PORT);					//配置端口
    	server.sin_family = AF_INET;					//设置协议为ipv4
    	server.sin_addr.s_addr = htonl(INADDR_ANY);		//设置监听地址为任意
    	
    	//绑定地址结构bind
    	if(bind(listenfd,(struct sockaddr *)&server,sizeof(server)) ==-1){
    		perror("Bind error!\n");
    		exit(0);
    	}
    	
    	//监听套接字.listen
    	//监听套接字只负责建立连接,无法用来收发数据
    	//listen函数使得套接字listenfd变为了被动套接字,也即是可以接受连接请求的套接字
    	//主动套接字是发送连接请求或者数据的
    	if(listen(listenfd,BACKLOG) ==-1){
    		//BACKLOG为最大连接数,可自定义
    		//对于这个单进程单线程并且不是IO复用的服务器其实同时只能和一个客户端建立连接
    		perror("Listen error!\n");
    		exit(0);
    	}
    
    	//等待连接请求的到达,到达后接受连接请求.accept
    	//已连接套接字connectfd只能用来收发数据不能用来接受连接请求
    	if((connectfd = accept(listenfd,(struct sockaddr *)&client,&clientlen)) ==-1){
    		//接受连接后客户端的地址结构会存储在client结构体中
    		perror("Accept error!\n");
    		exit(0);
    	}
    
    	//成功建立连接
    	//打印连接信息(客户端ip和连接端口)
    	printf("The connection from %s:%d\n",inet_ntoa(client.sin_addr),htons(client.sin_port));
    	//inet_ntoa的作用是将ip转化为点分十进制的形式
    	
    	while(1){
    		//清空recvbuf
    		memset(recvbuf,'\0',sizeof(recvbuf));
    		
    		if((num = recv(connectfd,recvbuf,MAXDATASIZE,0)) ==-1){
    			perror("Recv error!\n");
    			exit(0);
    		}
    
    		if(!strcmp(recvbuf,"quit")){
    			//如果客户端发来的消息为quit就退出
    			break;
    		}
    
    		else{
    			recvbuf[num] = '\0';
    
    			printf("Message:%s\n",recvbuf);
    
    			//调用自定义的反转字符串函数
    			reverse(recvbuf,num);
    			//将更改后的字符串发送给客户端
    			send(connectfd,recvbuf,num,0);
    		}//else end
    	
    	}//while end
    
    	//关闭已连接套接字
    	close(connectfd);
    	//关闭监听套接字
    	close(listenfd);
    	return 0;
    }
    
    char *reverse(char *str,int len){
    	//自定义的字符串反转函数
    	char *start = str;
    	char *end = str +len -1;
    	char ch;
    
    	if(str != NULL){
    		while(start < end){
    			ch = *start;
    			*start++ = *end;
    			*end-- = ch;
    		}
    	}
    	return str;
    }
    
    

    客户端代码如下

    #include <stdio.h>							//基本输入输出
    #include <netinet/in.h>						//地址结构体所在头文件
    #include <sys/socket.h>						//套接字函数所在头文件
    #include <netdb.h>							//hostent结构体所在头文件
    #include <stdlib.h>							//exit函数所在头文件
    #include <string.h>							//字符串处理函数
    #include <arpa/inet.h>						//inet_ntoa函数所在头文件
    #include <unistd.h>							//close函数所在头文件
    
    #define PORT 3333
    #define MAXDATASIZE 100
    
    int main(int argc,char *argv[]){			//main函数中加入了形参,可以从控制台获取参数
    	int sockfd;								//客户端只需要一个套接字
    	char local_ip[20],serv_ip[20];			
    	char recvbuf[MAXDATASIZE];
    	struct sockaddr_in server,local,serv;				//地址结构
    	struct hostent *he;						//存放gethostbyname返回的服务端信息
    	socklen_t locallen,servlen;
    
    	/*客户端要从终端获取服务器ip地址,先判断客户端执行程序是否正确
    	*传参方式如下
    	*客户端在终端执行程序:   ./client 127.0.0.1
    	*argv[0]的值便是  ./client
    	*argv[1]的值便是  127.0.0.1
    	*argc的值为2
    	*/
    
    	if(argc != 2){
    		//argc为参数个数,argv数组里存放的便是实际的参数
    		printf("Usage:%s <IP Address>\n",argv[0]);
    		exit(0);
    	}
    	
    	/*通过客户端输入的ip获取服务端信息.gethostbyname
    	*gethostbyname返回一个hostent结构体
    	*hostent结构体为
    	*
    	*struct hostent{
    	*	char *h_name;			//主机的正式名称
    	*	char **h_aliases;		//主机的别名列表
    	*	short h_addrtype;		//主机地址类型
    	*	short h_length;			//主机地址长度
    	*	char **h_addr_list;		//主机ip地址列表
    	*}
    	*并且有定义宏
    	*#define h_addr h_addr_list[0]
    	*也即是对于该结构体,h_addr对应了h_addr_listp0[
    	*/
    	if((he = gethostbyname(argv[1])) ==NULL){
    		perror("Gethostbyname error!\n");
    		exit(0);
    	}
    
    	//创建套接字.socket
    	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) ==-1){
    		//错误处理
    		perror("Socket error!\n");
    		exit(0);
    	}
    
    	//配置服务端地址结构,这样在发起连接时客户端才知道和谁建立连接
    	memset(&server,'\0',sizeof(server));						//清空
    	server.sin_port = htons(PORT);								//指定端口
    	server.sin_family = AF_INET;								//指定协议
    	server.sin_addr = *((struct in_addr *)he->h_addr);	//指定服务器
    
    	//可以发起连接(客户端不需要绑定地址结构).connect
    	if(connect(sockfd,(struct sockaddr *)&server,sizeof(server)) ==-1){
    		//connect函数第一个参数为指定用来建立连接的套接字,第二个参数则是通过服务端地址结构体来指明要建立连接的对象,第三个参数为地址结构体的大小
    		perror("Connect error!\n");
    		exit(0);
    	}
    
    	//题目要求要用getsockname和getpeername函数,故有下面的代码,不用则不需要
    	memset(&local,'\0',sizeof(local));
    	locallen = sizeof(local);
    	getsockname(sockfd,(struct sockaddr *)&local,&locallen);
    	/*函数原型
    	*getsockname(int sockfd,struct sockaddr *addr,socklen_t addrlen)
    	*第二个参数为用来存放信息的结构体,
    	*该函数会把本地ip和端口等信息存放在结构体local中
    	**/
    
    	memset(&serv,'\0',sizeof(serv));
    	servlen = sizeof(serv);
    	getpeername(sockfd,(struct sockaddr *)&serv,&servlen);
    	/*函数原型
    	*getpeername(int sockfd,struct sockaddr *addr,socklen_t addrlen)
    	*该函数会把sockfd所连接的远程服务器ip和端口存放至结构体serv中
    	*/
    
    	inet_ntop(AF_INET,&local.sin_addr,local_ip,sizeof(local_ip));
    	inet_ntop(AF_INET,&serv.sin_addr,serv_ip,sizeof(serv_ip));
    
    	//打印本机和远程服务器信息
    	printf("local ip:port is %s:%d\n",inet_ntoa(local.sin_addr),htons(local.sin_port));
    	printf("remote ip:port is %s:%d\n",inet_ntoa(serv.sin_addr),htons(serv.sin_port));
    
    	//下面进行交互.一般情况下,建立连接之后便可以进行交互
    	while(1){
    		printf("Plz enter some messages:");
    		memset(recvbuf,'\0',sizeof(recvbuf));
    		
    		scanf("%s",&recvbuf);
    		if(send(sockfd,recvbuf,strlen(recvbuf),0) ==-1){
    			perror("Send error!\n");
    			exit(0);
    		}
    
    		//如果输入的是quit则退出
    		if(!strcmp(recvbuf,"quit")){
    			printf("Bye~\n");
    			break;
    		}
    
    		//如果不是quit则接收服务端发送回来的已处理过的字符串
    		if(recv(sockfd,recvbuf,MAXDATASIZE,0) ==-1){
    			printf("Recv error!\n");
    			exit(0);
    		}
    		
    		printf("Reversed message:%s\n",recvbuf);
    
    	}//while end
    	
    	close(sockfd);
    	return 0;
    }
    
    展开全文
  • 基于 TCP网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下:connect()函数对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由...

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


    connect()函数

    对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接(三次握手详情,请看《浅谈 TCP 三次握手》),最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。


    通常的情况,客户端的 connect() 函数默认会一直阻塞,直到三次握手成功或超时失败才返回(正常的情况,这个过程很快完成)。


    listen()函数

    对于服务器,它是被动连接的。举一个生活中的例子,通常的情况下,移动的客服(相当于服务器)是等待着客户(相当于客户端)电话的到来。而这个过程,需要调用listen()函数。

    #include<sys/socket.h>
    int listen(int sockfd, int backlog);


    listen() 函数的主要作用就是将套接字( sockfd )变成被动的连接监听套接字(被动等待客户端的连接),至于参数 backlog 的作用是设置内核中连接队列的长度(这个长度有什么用,后面做详细的解释),TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。


    这里需要注意的是,listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。


    这样的话,当有一个客户端主动连接(connect()),Linux 内核就自动完成TCP 三次握手,将建立好的链接自动存储到队列中,如此重复。


    所以,只要 TCP 服务器调用了 listen(),客户端就可以通过 connect() 和服务器建立连接,而这个连接的过程是由内核完成



    下面为测试的服务器和客户端代码,运行程序时,要先运行服务器,再运行客户端:

    服务器:

    #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 = 8000;	
    
    	int sockfd;
    	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;
    	my_addr.sin_port   = htons(port);
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	
    	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);
    	
    	sleep(10);	// 延时10s
    
    	system("netstat -an | grep 8000");	// 查看连接状态
    	
    	return 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 = 8000;        		// 服务器的端口号
    	char *server_ip = "10.221.20.12";    	// 服务器ip地址
    
    	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;
    	server_addr.sin_port = htons(port);
    	inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
    	
    	int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));      // 主动连接服务器
    	if(err_log != 0)
    	{
    		perror("connect");
    		close(sockfd);
    		exit(-1);
    	}
    	
    	system("netstat -an | grep 8000");	// 查看连接状态
    	
    	while(1);
    
    	return 0;
    }
    

    运行程序时,要先运行服务器,再运行客户端,运行结果如下:


    三次握手的连接队列

    这里详细的介绍一下 listen() 函数的第二个参数( backlog)的作用:告诉内核连接队列的长度。


    为了更好的理解 backlog 参数,我们必须认识到内核为任何一个给定的监听套接口维护两个队列:

    1、未完成连接队列(incomplete connection queue),每个这样的 SYN 分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接口处于 SYN_RCVD 状态。


    2、已完成连接队列(completed connection queue),每个已完成 TCP 三次握手过程的客户对应其中一项。这些套接口处于 ESTABLISHED 状态。


     

    当来自客户的 SYN 到达时,TCP 在未完成连接队列中创建一个新项,然后响应以三次握手的第二个分节:服务器的 SYN 响应,其中稍带对客户 SYN 的 ACK(即SYN+ACK),这一项一直保留在未完成连接队列中,直到三次握手的第三个分节(客户对服务器 SYN 的 ACK )到达或者该项超时为止(曾经源自Berkeley的实现为这些未完成连接的项设置的超时值为75秒)。


    如果三次握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。


    backlog 参数历史上被定义为上面两个队列的大小之和,大多数实现默认值为 5,当服务器把这个完成连接队列的某个连接取走后,这个队列的位置又空出一个,这样来回实现动态平衡,但在高并发 web 服务器中此值显然不够


    accept()函数

    accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。


    如果,服务器不能及时调用 accept() 取走队列中已完成的连接,队列满掉后会怎样呢?UNP(《unix网络编程》)告诉我们,服务器的连接队列满掉后,服务器不会对再对建立新连接的syn进行应答,所以客户端的 connect 就会返回 ETIMEDOUT。但实际上Linux的并不是这样的!


    下面为测试代码,服务器 listen() 函数只指定队列长度为 2,客户端有 6 个不同的套接字主动连接服务器,同时,保证客户端的 6 个 connect()函数都先调用完毕,服务器的 accpet() 才开始调用。


    服务器:

    #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 = 8000;			
    	
    	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;
    	my_addr.sin_port   = htons(port);
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	
    	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, 2);	// 等待队列为2
    	if(err_log != 0)
    	{
    		perror("listen");
    		close(sockfd);		
    		exit(-1);
    	}	
    	printf("after listen\n");
    	
    	sleep(20);	//延时 20秒
    	
    	printf("listen client @port=%d...\n",port);
    
    	int i = 0;
    	
    	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("-----------%d------\n", ++i);
    		printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
    		
    		char recv_buf[512] = {0};
    		while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 )
    		{
    			printf("recv data ==%s\n",recv_buf);
    			break;
    		}
    		
    		close(connfd);     //关闭已连接套接字
    		//printf("client closed!\n");
    	}
    	close(sockfd);         //关闭监听套接字
    	return 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>
    
    void test_connect()
    {
    	unsigned short port = 8000;        		// 服务器的端口号
    	char *server_ip = "10.221.20.12";    	// 服务器ip地址
    	
    	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;
    	server_addr.sin_port = htons(port);
    	inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
    	
    	int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));      // 主动连接服务器
    	if(err_log != 0)
    	{
    		perror("connect");
    		close(sockfd);
    		exit(-1);
    	}
    	
    	printf("err_log ========= %d\n", err_log);
    	
    	char send_buf[100]="this is for test";
    	send(sockfd, send_buf, strlen(send_buf), 0);   // 向服务器发送信息
    	
    	system("netstat -an | grep 8000");  // 查看连接状态
    	
    	//close(sockfd);
    }
    
    int main(int argc, char *argv[])
    {
    	pid_t pid;
    	pid = fork();
    	
    	if(0 == pid){
    
    		test_connect();		// 1
    		
    		pid_t pid = fork();
    		if(0 == pid){
    			test_connect();	// 2
    		
    		}else if(pid > 0){
    			test_connect();	// 3
    		}
    		
    	}else if(pid > 0){
    		
    		test_connect();	// 4
    		
    		pid_t pid = fork();
    		if(0 == pid){
    			test_connect();	// 5
    		
    		}else if(pid > 0){
    			test_connect();	// 6
    		}
    	
    	}
    
    	while(1);
    	
    	return 0;
    }

    同样是先运行服务器,在运行客户端,服务器 accept()函数前延时了 20 秒, 保证了客户端的 connect() 全部调用完毕后再调用 accept(),运行结果如下:

    服务器运行效果图:




    客户端运行效果图:



    按照 UNP 的说法,连接队列满后(这里设置长度为 2,发了 6 个连接),以后再调用 connect() 应该统统超时失败,但实际上测试结果是:有的 connect()立刻成功返回了,有的经过明显延迟后成功返回了。对于服务器 accpet() 函数也是这样的结果:有的立马成功返回,有的延迟后成功返回。


    对于上面服务器的代码,我们把lisen()的第二个参数改为 0 的数,重新运行程序,发现:

    客户端 connect() 全部返回连接成功(有些会延时):



    服务器 accpet() 函数却不能把连接队列的所有连接都取出来:



    对于上面服务器的代码,我们把lisen()的第二个参数改为大于 6 的数(如 10),重新运行程序,发现,客户端 connect() 立马返回连接成功, 服务器 accpet() 函数也立马返回成功。


    TCP 的连接队列满后,Linux 不会如书中所说的拒绝连接,只是有些会延时连接,而且accept()未必能把已经建立好的连接全部取出来(如:当队列的长度指定为 0 ),写程序时服务器的 listen() 的第二个参数最好还是根据需要填写,写太大不好(具体可以看cat /proc/sys/net/core/somaxconn,默认最大值限制是 128),浪费资源,写太小也不好,延时建立连接。


    测试代码下载请点此处。



    展开全文
  • TCP介绍及TCP网络编程

    千次阅读 2017-11-12 00:10:55
    一、TCP头部结构: ①16位端口号及16位目的端口号:告知主机该报文段来自哪里(源端口)要传给那个上层协议或应用程序(目的端口)。 ②32位序号:一次TCP通信过程中某一个传输方向上的字节流的每个字节的编号。...

    一、TCP头部结构:
    这里写图片描述
    ①16位端口号及16位目的端口号:告知主机该报文段来自哪里(源端口)要传给那个上层协议或应用程序(目的端口)。
    ②32位序号:一次TCP通信过程中某一个传输方向上的字节流的每个字节的编号。A发送给B的第一个报文段中,序号值被系统初始化为某个随机值ISN,后续在该方向上的TCP报文段的序号值被设置为ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。
    ③32位确认号:另一方对TCP报文段的响应,其值是收到的TCP报文段的序号值加1。
    ④4位头部长度:标识该TCP头部有多少个32位(4字节,即有多少行)。4位最大能表示15,所以TCP头部最长是60字节。
    ⑤6位标志:
    5.1 URG标志:紧急指针是否有效。
    5.2 ACK标志:确认号是否有效。确认报文段。
    5.3 PSH标志:提示接收端应用程序应该立即从TCP接受缓冲区中读走数据,为接受后续数据腾出空间。TCP缓冲区 在网卡上,有大小限制,如果应用程序一直不读取就会一直在缓冲区,影响后续数据。
    5.4 RST标志:表示要求对方重新建立连接。复位报文段。
    5.5 SYN标志:请求建立连接。同步报文段。
    5.6 FIN标志:通知对方本段要关闭连接。结束报文段。
    ⑥16位窗口大小:TCP流量控制。接收通告窗口告诉对方本端的TCP缓冲区还能接收多大字节的数据,避免发过来接收不了而造成的数据丢失。
    ⑦16位校验和:检验TCP报文段在传输过程中是否损坏。
    ⑧16位紧急指针:紧急数据先处理。

    二、TCP三次握手以及四次挥手(本文都以客户端先发送请求为例)
    这里写图片描述
    三次握手建立连接:
    ①请求连接方(客户端)发送SYN请求连接;
    ②服务器返回SYN/ACK确认收到发送端请求;
    ③客户端回馈给服务器ACK,表示确认收到服务器发送的确认。

    注:1、如果服务器没有收到客户端发送的ACK,则启动超时重传机制,这确保了TCP连接的准确性。
    2、TCP的握手至少有三次,不可再少。
    

    四次挥手断开连接:
    ①请求方(客户端)数据已经发送完毕,向服务器发送FIN请求断开连接;
    ②服务器向客户端发送ACK,对客户端发送的FIN进行确认,并不需要即使断开;
    ③服务器将接收到的数据处理完毕后发送FIN,断开连接;
    ④客户端发送确认消息ACK。

    注:1、应答方(服务器)给在请求方(客户端)发送第一个ACK之后,可能还需要一段时间来处理请求方发送的数据。
    2、如果在应答方(服务器)给请求方(客户端)发送第一个ACK后,服务器可能已经处理完了所有的数据,此时四次挥手可以缩短为三次。
    3、TIME_WAIT状态:出现在主动发起断开链接请求的一端。
    意义:
        3.1、 保证可靠的终止TCP链接
        3.2、 保证迟来的数据报能被识别并丢弃
    

    三、总状态转移图
    这里写图片描述这里写图片描述

    四、TCP编程
    服务器:
    1、创建socket
    2、bind(命名IP地址和端口号)
    3、listen(创建监听队列)
    4、accept(拿到已经完成连接的)
    5、recv/send(收发数据)
    6、close(关闭)

    客户端:
    1、socket
    2、connect
    3、recv/send
    4、close

    ①创建套接字函数:int socket(int domain, int type, int protocol)
    domain:协议簇 IPv4(AF_INET)或 IPv6(AF_INET6) 
    type:选择协议 TCP:SOCK_STREAM     UDP:SOCK_DGRAM   
    protocol:0  
    
    ②命名IP地址和端口号:int bind(int sockfd,const struct sockaddr*addr,int addrlen)
    sockfd:文件描述符 
    addr:指定IP地址和端口号   
    struct sockaddr_in
    {  
        sa_family_t sin_family;   //地址簇:AF_INET
        u_int16_t sin_port;       //端口号(网络字节序:大端模式 PC机:小端模式)
        struct in_addr sin_addr; //IPv4地址结构体
    };  
    struct sin_addr
    {  
           u_int32_t s_addr;        //IPv4地址用网络字节序表示
    };  
    addrlen:该socket地址的长度
    
    ③创建一个监听队列:int listen(int sockfd, int size) 
    size:队列大小,默认值5 。
    
    ④int accept(int sockfd,struct sockaddr *addr ,int *addrlen)
    addr:要连接的服务器的ip地址及端口号(netstat -atp 显示本机上所有的tcp服务程序以及其占用的端口号)
    
    ⑤recv(c,buff,127,0)
    

    代码实现
    ser.c

     void main()
     {
         int sockfd = socket(AF_INET,SOCK_STREAM,0);
         assert(sockfd != -1);
    
         struct sockaddr_in ser,cli;
         ser.sin_family = AF_INET;
         ser.sin_port = htons(6000);
         ser.sin_addr.s_addr = inet_addr("127.0.0.1");
    
          int res = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
          assert(res != -1);
    
          listen(sockfd,5);
    
          while(1)
          {
              int len = sizeof(cli);
              int c = accept(sockfd,(struct sockaddr*)&cli,&len);
              assert(c != -1);
    
              char buff[128] = {0};
              recv(c,buff,127,0);
              printf("recv::%s\n",buff);
              send(c,"I Know",strlen("I Know"),0);
    
              close(c);
          }
          close(sockfd);
      }

    cli.c

    //客户端cli.c
    void main()
     {
         int sockfd = socket(AF_INET,SOCK_STREAN,0);
          assert(sockfd != -1);
    
          struct sockaddr_in ser,cli;
          ser.sin_family = AF_INET;
         ser.sin_port = htons(6000);
          ser.sin_addr.s_addr = inet_addr("127.0.0.1");
    
          int res = connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
          assert(res != -1);
    
          send(sockfd,"Hello World",strlen("Hello World"),0);
          char buff[128] = {0};
          recv(sockfd,buff,127,0);
    
          printf("recv::%s\n",buff);
    
          close(sockfd);
      }
    
    展开全文
  • 第一章 理解网络编程和套接字 套接字在网络编程中的作用是什么?为什么称它为套接字? P2,网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下,我们只需要考虑如何编写传输...

    第一章 理解网络编程和套接字

    1. 套接字在网络编程中的作用是什么?为什么称它为套接字?

      P2,网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下,我们只需要考虑如何编写传输软件。操作系统提供了名为“套接字”,套接字是网络传输传输用的软件设备

      socket英文原意是插座:我们把插头插到插座上就能从电网获得电力供给,同样,为了与远程计算机进行数据传输,需要连接到Internet,而变成中的“套接字”就是用来连接该网络的工具

    2. 在服务器端创建套接字后,会依次调用listen函数和accept函数。请比较并说明两者作用

      listen:将套接字转为可接受连接方式

      accept:受理连接请求,并且在没有连接请求的情况调用该函数,不会返回。直到有连接请求为止。二者存在逻辑上的先后关系

    3. Linux中,对套接字数据进行I/O时可以直接使用I/O相关函数;而在Windows中则不可以。原因为何?

      Linux把套接字也看作是文件,所以可以用文件I/O相关函数;而Windows要区分套接字和文件,所以设置了特殊的函数

    4. 创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?

      要在网络上区分来自不同机器的套接字,所以需要地址信息。分配地址是通过bind()函数实现

    5. Linux中的文件描述符与Windows的句柄实际上非常类似。请以套接字为对象说明他们的含义。

      Linux的文件描述符是为了区分指定文件而赋予文件的整数值(相当于编号)。Windows的文件描述符其实也是套接字的整数值,其目的也是区分指定套接字。

    6. 底层文件I/O函数与ANSI标准定义的文件I/O函数之间有何区别?

      ANSI标准定义的输入、输出函数是与操作系统(内核)无关的以C标准写成的函数。相反,底层文件I/O函数是直接提供的。理论上ANSI标准I/O提供了某些机制,性能上由于底层I/O

    7. 参考本书给出的示例low_open.c和low_read.c,分别利用底层文件I/O和ANSI标准I/O编写文件复制程序。可任意指定复制程序的使用方法

    第二章:套接字类型与协议设置

    1. 什么是协议?在收发数据中定义协议有何意义?

      协议就是为了完成数据交换而定好的约定。因此,定义协议意味着对数据传输所必需的的承诺进行定义。

    2. 面向连接的TCP套接字传输特性有3点,请分别说明。

      • 传输过程中数据不会丢失
      • 按序传输数据
      • 传输的数据不存在数据边界(Boundary)
    3. 下面哪些是面向消息的套接字的特性?a、c、e

    4. UDP 、TCP 、TCP

    5. 何种类型的套接字不存在数据边界?这类套接字接收数据时需要注意什么?

      连接指向型TCP套接字不存在数据边界。因此输入输出函数的响应次数不具有意义。重要的不是函数的响应次数,而是数据的收发量。因此,必须将传输数据的量和接收数据的量制作成编码,保证发送数据的量和接收数据的量是一致的,特别要注意是制作依赖函数响应次数判断代码

    6. 修改代码

    /*****************************tcp_serv.c*********************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    void error_handling(char *message);
    
    int main(int argc, char *argv[])
    {
    	int serv_sock;
    	int clnt_sock;
    
    	struct sockaddr_in serv_addr;
    	struct sockaddr_in clnt_addr;
    	socklen_t clnt_addr_size;
    
    	char message[]="Hello World!";
    	
    	if(argc!=2){
    		printf("Usage : %s <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    	if(serv_sock == -1)
    		error_handling("socket() error");
    	
    	memset(&serv_addr, 0, sizeof(serv_addr));
    	serv_addr.sin_family=AF_INET;
    	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    	serv_addr.sin_port=htons(atoi(argv[1]));
    	
    	if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
    		error_handling("bind() error"); 
    	
    	if(listen(serv_sock, 5)==-1)
    		error_handling("listen() error");
    	
    	clnt_addr_size=sizeof(clnt_addr);  
    	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
    	if(clnt_sock==-1)
    		error_handling("accept() error");  
    	
    	write(clnt_sock, message, 4);
    	write(clnt_sock, message+4, 4);
    	write(clnt_sock, message+8, 4);
    	write(clnt_sock, message+12, sizeof(message)-12);
    
    	close(clnt_sock);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    
    /*****************************tcp_clnt.c*********************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    void error_handling(char *message);
    
    int main(int argc, char* argv[])
    {
    	int sock;
    	struct sockaddr_in serv_addr;
    	char message[30];
    	int str_len=0;
    	int idx=0, read_len=0, i;
    	
    	if(argc!=3){
    		printf("Usage : %s <IP> <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	sock=socket(PF_INET, SOCK_STREAM, 0);
    	if(sock == -1)
    		error_handling("socket() error");
    	
    	memset(&serv_addr, 0, sizeof(serv_addr));
    	serv_addr.sin_family=AF_INET;
    	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    	serv_addr.sin_port=htons(atoi(argv[2]));
    		
    	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) 
    		error_handling("connect() error!");
    
    	for(i=0; i<100; i++)		// busy waiting!!
    		printf("Wait time %d \n", i);
    
    	read(sock, message, sizeof(message));
    	printf("Message from server: %s \n", message);
    	close(sock);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    

    第三章 地址族与数据序列

    1. IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?

      IPV4是4字节地址族,IPV6是16字节地址族。IPV6的诞生是为了应对2010年前后IP地址耗尽的问题而提出的标准

    2. 通过IPV4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程

      首先数据传输的第一个环节是向目标IP所属的网络传输数据。此时使用的是IP地址中的网络ID。传输的数据将被传到管理网络的路由器,接受数据的路由器将参照IP地址的主机号找自己保存的路由表,找到对应的主机发送数据

    3. 套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?

      IP地址是为了区分网络上的主机。端口号是区分同一主机下的不同的SOCKET,以确保软件准确收发数据。

    4. CAB

    5. 计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用

      路由器是帮助数据传输到目的地的中介。不仅如此,还起到帮助连接本地网络的电脑和互联网的作用

    6. 什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP合同FTP端口号各是多少?

      “知名端口(Well-known PROT)”是指预定分配给特定操作的端口。其范围是0~1023,其中最知名的端口是HTTP:80端口和TCP:21

    7. 题目大概意思是:为什么bind中第二个参数是sockaddr,但是传入的是sockaddr_in

    bind函数第二个参数类型是sockaddr结构体,很难份分配IP地址和端口号,因此IP地址和PORT号的分配是通过sockaddr_in完成的。因为该结构体和sockaddr结构体的组成字节序和大小完全相同,所以可以强转

    1. 请解释大端序、小端序、网络字节序,并说明为何需要网络字节序

      小端序是把高位字节存储到高位地址上;大端序是把高位字节存储到低位地址上。因为保存栈的方式有差异,所以对网络传输数据的过程制定了标准,这就是“网路字节序”。而且,在网络字节序中,数据传输的标准是“大端序”

    2. 大端计算机希望将4字节整型数据12传到小端序计算机。请说出数据传输过程中发生的字节序变换过程

      因为网络字节序的顺序标准是“大端序”,所以大端序的计算机在网络传输中不需要先转换字节顺序,直接传输。但是接受数据的是小端序计算机,因此,要经过网络转本地序的过程,再保存到存储设备上

    3. 怎么表示回送地址?其含义是什么?如果向回送地址传输数据将会发生什么情况?

      回送地址表示计算机本身,为127.0.0.1。因此,如果将数据传送到IP地址127.0.0.1,数据会不会通过传输到网络的其他设备上而直接返回。

    第四章 基于TCP的服务器端/客户端(1)

    1. 请说明TCP/IP的4层协议栈,并说明TCP和UDP套接字经过的层级结构差异

      链路层—>IP层—>TCP层—>应用层

      链路层—>IP层—>UDP层—>应用层

    2. 请说出TCP/IP协议栈中链路层和IP层的作用,并给出两者关系。

      链路层是LAN、WAN、MAN等网络标准相关的协议栈,是定义物理性质标准的层级。相反,IP层是定义网络传输数据标准的层级。即IP层负责以链路层为基础的数据传输

    3. 为何需要把TCP/IP协议栈分成4层(或7层)?结合开放式系统回答

      将复杂的TCP/IP协议分层化的话,就可以将分层的层级标准发展成开放系统。实际上,TCP/IP是开放系统,各层级都被初始化,并以该标准为依据组成了互联网。因此,按照不同层级标准,硬件和软件可以相互替代,这种标准化是TCP/IP蓬勃发张的依据

    4. 客户端调用connect函数向服务器端发送连接请求。服务器端调用哪个函数后,客户端可以调用connect函数?

      listen函数,客户端才可以调用connect函数

    5. 什么时候创建连接请求等待队列?它有何作用?与accept有什么关系

      listen函数的调用创建了请求等待队列。它是存储客户端连接请求信息的空间。accept函数调用后,将从本地存储的连接请求信息取出,与客户端建立连接。

    6. 客户端中为何不需要调用bind函数分配地址?如果不调用bind函数,那何时、如何向套接字分配IP地址和端口号?

      客户端是请求连接的程序,不是一个接收连接的程序。所以,指导服务器的地址信息是更重要的因素,没有必要通过bind函数明确地分配地址信息。但是,要想和服务器通信,必须将自己的地址信息分配到套接字上,因此,在connect函数调用时,自动把IP地址和端口号输入到套接字上

    7. 改成迭代服务器端

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    void error_handling(char *message);
    
    int main(int argc, char *argv[])
    {
    	int serv_sock;
    	int clnt_sock;
    
    	struct sockaddr_in serv_addr;
    	struct sockaddr_in clnt_addr;
    	socklen_t clnt_addr_size;
    
    	char message[]="Hello World!";
    	
    	if(argc!=2){
    		printf("Usage : %s <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    	if(serv_sock == -1)
    		error_handling("socket() error");
    	
    	memset(&serv_addr, 0, sizeof(serv_addr));
    	serv_addr.sin_family=AF_INET;
    	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    	serv_addr.sin_port=htons(atoi(argv[1]));
    	
    	if( bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1 )
    		error_handling("bind() error"); 
    	
    	if( listen(serv_sock, 5)==-1 )
    		error_handling("listen() error");
    	
    	clnt_addr_size=sizeof(clnt_addr);  
    	
    	while(1)
    	{
    		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
    		if(clnt_sock==-1)
    			break;  
    		
    		write(clnt_sock, message, sizeof(message));
    		close(clnt_sock);
    	}
    	close(serv_sock);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    
    

    第五章 基于TCP的服务器端/客户端(2)

    1. 请说明TCP套接字连接设置的三次握手过程。尤其是3次数据交换过程每次收发的数据内容。

      网上有很多,就不一一写了

    2. TCP是可靠的数据传输协议,但在通过网络通信的过程可能丢失数据。请通过ACK和SEQ说明TCP通过何种机制保证丢失数据的可靠传输。

      SEQ顺序标识符是给信息编号。ACK是用于回复带有编号的信息。也就是说,每次传输信息时,都同时发送SEQ标识,而受到信息的主机应以SEQ信息为基础回复发送信息的主机。通过这种机制,传输数据的主机就可以确认数据是否被正确接收。在传输失败时,可以重新传送。

    3. TCP套接字中调用write和read函数时数据如何移动?结合I/O缓冲进行说明

      当write函数被调用时,数据就会向端口的输出缓冲区移动。然后经过网络传输传输到对方主机套接字的输入缓冲。这样,输入缓冲中存储的数据通过read函数的响应来读取

    4. 对方主机的输入缓冲剩余50字节空间时,若本方主机通过write函数请求传输70字节,问TCP如何处理这种情况?

      对方主机会把输入缓冲中可存储的数据大小传送给要传输数据的数据(本方)。因此,在剩余空间为50字节的情况,即使要求传送70字节的数据,也不能传输50字节以上,剩余的部分保存在传输方的输出缓冲中,等待对方主机的输入缓冲出现空间。而且,这种交换缓冲多余空间信息的协议被称为滑动窗口协议

    5. 更改程序,使服务器端和客户端各传送1次字符串…

    /**********************************sendrecv_serv.c***********************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    void error_handling(char *message);
    
    int main(int argc, char *argv[])
    {
    	int serv_sock;
    	int clnt_sock;
    	int str_len, i;
    
    	struct sockaddr_in serv_addr;
    	struct sockaddr_in clnt_addr;
    	socklen_t clnt_addr_size;
    
    	char msg1[]="Hello client!";
    	char msg2[]="I'm server.";
    	char msg3[]="Nice to meet you.";
    	char* str_arr[]={msg1, msg2, msg3};
    	char read_buf[100];
    	
    	if(argc!=2){
    		printf("Usage : %s <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    	if(serv_sock == -1)
    		error_handling("socket() error");
    	
    	memset(&serv_addr, 0, sizeof(serv_addr));
    	serv_addr.sin_family=AF_INET;
    	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    	serv_addr.sin_port=htons(atoi(argv[1]));
    	
    	if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
    		error_handling("bind() error"); 
    	
    	if(listen(serv_sock, 5)==-1)
    		error_handling("listen() error");
    	
    	clnt_addr_size=sizeof(clnt_addr);  
    	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
    	if(clnt_sock==-1)
    		error_handling("accept() error");  
    	
    	for(i=0; i<3; i++)
    	{
    		str_len=strlen(str_arr[i])+1;
    		write(clnt_sock, (char*)(&str_len), 4);
    		write(clnt_sock, str_arr[i], str_len);
    		
    		read(clnt_sock, (char*)(&str_len), 4);
    		read(clnt_sock, read_buf, str_len);
    		puts(read_buf);
    	}
    	
    	close(clnt_sock);
    	close(serv_sock);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    
    /***************************************recvsend_clnt.c***************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    void error_handling(char *message);
    
    int main(int argc, char* argv[])
    {
    	int sock;
    	struct sockaddr_in serv_addr;
    
    	char msg1[]="Hello server!";
    	char msg2[]="I'm client.";
    	char msg3[]="Nice to meet you too!";
    	char* str_arr[]={msg1, msg2, msg3};
    	char read_buf[100];
    
    	int str_len, i;
    	
    	if(argc!=3){
    		printf("Usage : %s <IP> <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	sock=socket(PF_INET, SOCK_STREAM, 0);
    	if(sock == -1)
    		error_handling("socket() error");
    	
    	memset(&serv_addr, 0, sizeof(serv_addr));
    	serv_addr.sin_family=AF_INET;
    	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    	serv_addr.sin_port=htons(atoi(argv[2]));
    	
    	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) 
    		error_handling("connect() error!");
    
    	for(i=0; i<3; i++)
    	{
    		read(sock, (char*)(&str_len), 4);
    		read(sock, read_buf, str_len);
    		puts(read_buf);
    
    		str_len=strlen(str_arr[i])+1;
    		write(sock, (char*)(&str_len), 4);
    		write(sock, str_arr[i], str_len);
    	}
    	close(sock);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    
    
    1. 创建收发文件的服务器端/客户端
    /**********************************file_serv.c***********************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    #define BUF_SIZE 30
    void error_handling(char *message);
    
    int main(int argc, char *argv[])
    {
    	int serv_sd, clnt_sd;
    	FILE * fp;
    	char buf[BUF_SIZE];
    	char file_name[BUF_SIZE];
    	int read_cnt;
    	
    	struct sockaddr_in serv_adr, clnt_adr;
    	socklen_t clnt_adr_sz;
    	
    	if(argc!=2) {
    		printf("Usage: %s <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	serv_sd=socket(PF_INET, SOCK_STREAM, 0);   
    	
    	memset(&serv_adr, 0, sizeof(serv_adr));
    	serv_adr.sin_family=AF_INET;
    	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    	serv_adr.sin_port=htons(atoi(argv[1]));
    	
    	bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    	listen(serv_sd, 5);
    	
    	clnt_adr_sz=sizeof(clnt_adr);    
    	clnt_sd=accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    	
    	read(clnt_sd, file_name, BUF_SIZE);
    	fp=fopen(file_name, "rb");
    	if(fp!=NULL)
    	{
    		while(1)
    		{
    			read_cnt=fread((void*)buf, 1, BUF_SIZE, fp);
    			if(read_cnt<BUF_SIZE)
    			{
    				write(clnt_sd, buf, read_cnt);
    				break;
    			}
    			write(clnt_sd, buf, BUF_SIZE);
    		}
    	}
    	
    	fclose(fp);
    	close(clnt_sd); close(serv_sd);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    
    
    /***************************************recvsend_clnt.c***************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    #define BUF_SIZE 30
    void error_handling(char *message);
    
    int main(int argc, char *argv[])
    {
    	int sd;
    	FILE *fp;
    	
    	char buf[BUF_SIZE];
    	char file_name[BUF_SIZE];
    	int read_cnt;
    	struct sockaddr_in serv_adr;
    	if(argc!=3) {
    		printf("Usage: %s <IP> <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	printf("Input file name: ");
    	scanf("%s", file_name);
    	fp=fopen(file_name, "wb");
    
    	sd=socket(PF_INET, SOCK_STREAM, 0);   
    	memset(&serv_adr, 0, sizeof(serv_adr));
    	serv_adr.sin_family=AF_INET;
    	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    	serv_adr.sin_port=htons(atoi(argv[2]));
    
    	connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    	write(sd, file_name, strlen(file_name)+1);	
    
    	while((read_cnt=read(sd, buf, BUF_SIZE))!=0)
    		fwrite((void*)buf, 1, read_cnt, fp);
    	
    	fclose(fp);
    	close(sd);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    
    
    
    展开全文
  • TCP网络编程

    2020-10-12 21:40:56
    //客户端 @Test public void client() { Socket socket = null; OutputStream os = null; try { //1.创建Socket对象,指明服务器端... InetAddress inet = InetAddress.getByName("192.168.14.100");... socket = ne
  • TCP/IP网络编程基础知识

    千次阅读 2018-06-10 20:17:26
    1. Socket1)Socket简介a)一种编程接口,用于不同计算机之间通信的接口b)一种特殊的文件描述符c)并不局限于TCP/IP协议d)面向连接和面向无连接的Socket都存在e)独立于具体协议,TCP和UDP都可以使用2)Socket类型...
  • TCP网络编程流程

    2019-03-17 15:49:48
    先了解一些计算机网络的知识点: 国际标准化组织ISO于1977年成立,不久就提出一个试图使各种计算机在世界范围内互连成网的标准框架,即著名的开放系统互连基本参考模型 OSI/RM(Open Systems Interconnection ...
  • QT 之TCP网络编程(非常值得看的一篇博客!)

    万次阅读 多人点赞 2019-01-23 18:15:39
    首先介绍一下TCP:(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。相比而言UDP,就是开放式、无连接、不可靠的传输层通信协议。 下面,我一次进行客户端和...
  • Qt实现Winsock网络编程Tcp服务端和Tcp客户端通信(多线程) 前言 感觉Winsock网络编程的api其实和Linux下网络编程的api非常像,其实和其他编程语言的网络编程都差不太多。博主用Qt实现的,当然不想用黑窗口呗,...
  • Linux高级网络编程系列教程

    万次阅读 多人点赞 2015-06-19 11:39:44
    1、Linux网络编程01——网络协议入门 2、Linux网络编程02——无连接和面向连接的区别 3、Linux网络编程03——字节序和地址转换 4、Linux网络编程04——套接字 5、Linux网络编程05——C/S与B/S架构的区别 6、Linux...
  • TCP IP网络编程 (韩)尹圣雨pdf下载

    千次阅读 2015-07-15 16:42:54
    为初学者准备的网络编程! 韩国TCP/IP经典教程!手把手教你套接字编程! 本书涵盖操作系统、系统编程、TCP/IP协议等多种内容,结构清晰、讲解细致、通俗易懂。书中收录丰富示例,详细展现了Linux和Windows平台下...
  • 【Java TCP/IP Socket】Socket编程大合集

    万次阅读 多人点赞 2013-12-29 08:28:34
    为了方便各位网友学习以及方便自己复习之用,将Java TCP/IP Socket编程系列内容按照学习顺序总结如下: 【Java TCP/IP Socket】Java TCP Socket程编程 【Java TCP/IP Socket】Java UDP Socket编程 【Java TCP/IP ...
  • 说起TCP协议真的一大堆东西,TCP很复杂和网络编程息息相关,网络编程离不开TCP。虽然数据通过协议自己就发出去了,但是在排查网络问题时,我们都是通过抓包看协议本身。更好的学习tcp协议才能了解网络编程的数据传输...
  • 在学习Tcp协议编程中完成了通讯聊天功能,下面简单讲讲我最近学到的及Tcp聊天的源代码及详细注释。 Tcp协议是一个传输层的协议,在Tcp协议编程中它通常使用的是3个类,其命名空间为System.Net.Sockets: 下面是Tcp...
  • 在 Boolan 网开讲《Linux 网络编程实战》课程

    万次阅读 热门讨论 2015-03-30 14:51:05
    网络编程实战》是一门以讲解实例为主的课程,每一节都讲一两个网络编程的例子程序,课程偏重 Linux 服务端 TCP 网络编程。 本课程要求听课人员已经读过《Unix 网络编程》,能写简单的 TCP echo 服务。 课程地址:...
  • 基于TCP和UDP的Socket编程的步骤

    千次阅读 2019-07-30 11:10:51
    java为TCP和UDP两种通信协议提供了相应的Socket编程类,这些类存放在java.net包中。与TCP对应的是服务器端的ServerSocket和客户端的Socket;与UDP对应的是DatagramSocket. 基于TCP协议的Socket编程的主要步骤: ...
  • 计算机网络实验——基于TCP协议的socket编程

    万次阅读 多人点赞 2018-06-15 16:45:48
    2. 熟悉c++、Java等高级编程语言网络编程的基本操作。3. 基本了解对话框应用程序的编写过程。4. 实现TCP套接字编程。 二、实验内容(一)实验思路1、学习理解TCP协议。2、实现TCP客户端之间多线程通信以及聊天...
  • TCP和UDP的最完整的区别

    万次阅读 多人点赞 2016-08-04 11:30:30
    TCP和UDP两种协议的比较汇总
  • 《Windows 网络编程》 罗莉琴

    万次阅读 热门讨论 2017-06-10 02:15:51
    当初为了学习网络编程, 买了... 学习网络编程, 最重要的不是拿着厚厚的《unix网络编程》和《TCP/IP详解》死啃, 看天书, 看理论yy, 尽管很多网上所谓的“大牛”每次发言都不离《unix网络编程》和《TCP/IP详解》。
  • linux网络编程(完整版)

    万次阅读 2019-04-16 11:09:52
    之间在网上看到很多网络编程都是一个一个demo,今天我把之前学到的汇总起来,希望大家可以进行补充。 线程中我使用过两种方式编程,一种是经典函数式编程加上标志位,如下: while(1) { server_init(); ...
1 2 3 4 5 ... 20
收藏数 196,535
精华内容 78,614
关键字:

tcp网络编程