网络编程:linux下的socket套接字编程之TCP服务器

文章目录

前言

socket编程是一门网络编程技术,在主要的网络通信中都会使用到它,可以使用socket编程来实现计算机之间的通信。

一、Internet历史

1968:ARPAnet(阿帕网) 采用的协议 NCP(网络控制协议). internet的雏形

1.不能跨越系统,与平台.
2.不能对数据就行纠错.

1974: 第一份TCP(传输控制协议)协议详细说明.

1.协议在有数据包丢失时不能有效的纠正

1983:TCP/ip协议成为Internet的"世界语"

把之前的tcp协议分成两部分:
IP:  用于在互联网环境中找寻目标主机.
TCP: 对数据进行纠错.

二、网络体系结构

1.OSI模型

7 层协议模型 理想化模型.

	  1.应用层
	  2.表示层
	  3.会话层
	  4.传输层
	  5.网路层
	  6.数据链路层
	  7.物理层

2.TCP/IP模型

4 层协议模型 标准模型
 {
	  1.应用层: 
			作用:决定上层用户处理数据的方式
			http(超文本传输协议),FTP(文件传输协议),SMTP(邮件传输协议)
	  
	  2.传输层: 
			作用:提供端到端的连接(端口作用:标识进程)
			TCP(传输控制协议)
			UDP(用户数据报协议)  
			单位:数据包.
			
	  3.网络层:
			作用:提供数据包的路由. (标识主机)
			IP(网间协议): 网络环境中起到路由的作用(找目标主机) 
			ICMP(互联网控制信息协议):  
			IGMP:
			单位:分组形式.
				
	  4.网络接口层(数据链路与物理层): 
			作用:物理层面的通信(介质之间),
				  传输有地址的帧以及错误检测功能 
			以太网协议.
			物理层单位: 二进制流方式  位bit
			数据链路:数据帧.

}

三、网络设计框架

{
	c/s: 客户端 与 服务器
		 缺点:1.开发工作量大,调试麻烦.
			  2.用户安全隐患大.
		 优点:1.本地可以缓存数据.(技能特效)
			  2.协议比较灵活(可以自己制定)
		 
	b/s: 浏览器 与 服务器
		 优点:1.开发工作量小,调试较简单.
			  2.用户安全隐患小.
		 缺点:1.本地不可以缓存数据.
			  2.协议固定是http(要包含所有http协议内容)
}

四、TCP服务器编写流程

{
基础:

套接字:1.是一个网络通信的接口.
	   2.是一种特殊文件描述符(非负整数).
套接字类型:SOCK_STREAM: 流式套接字. 以二进制流方式依次发送.
								保证数据可靠
								默认采用tcp协议
		 SOCK_DGRAM:  数据报套接字.
								不保证数据可靠
								默认采用udp协议.
		 SOCK_RAW:    原始套接字.

流程:

1.创建套接字–socket() ----> 买手机

int socket(int domain, int type, int protocol);
{
		作用:创建一个用于网络通信的套接字.
		domain:
				AF_UNIX: 本地套接字协议(创建出的套接字只能本地通信)
				AF_INET: 选择ipv4协议族.
				AF_INET6: 选择ipv6协议族.
				IPv4:表示ip地址由32位二进制数据构成
				IPv6:表示ip地址由128位二进制数据构成
		type:
				SOCK_STREAM: 流式套接字
				SOCK_DGRAM:  数据报套接字
				
		protocol: 通常该参数都是以缺省方式 0 传参.
				例如: 第二个参数选择SOCK_STREAM,改参数传0
					   代表采用SOCK_STREAM的默认协议(TCP)
					   
		返回值:真确返回文件描述符(套接字)
				错误  -1
}

2.绑定套接字 bind() ----> 买卡以及与手机进行绑定

	  1.设置IP地址(标识主机)  2.设置端口号(标识进程)
	    struct sockaddr_in: 结构体作用 用于设置ipv4协议族 的ip地址与端口.
  struct sockaddr_in{
               sa_family_t    sin_family; // 选择协议族 AF_INET 
               in_port_t      sin_port;   // unsigned short  设置端口号.
               struct in_addr sin_addr;   // 结构体ip地址  要访问s_addr来进行设置
            };
		    struct in_addr {
               uint32_t       s_addr;     //s_addr 是ip地址的二进制形式.
            };
ip地址形式:点分十进制"192.168.2.2".这种形式是给用户看的计算机不认识
二进制形式 11000000 10101000 00000010 00000010 
 int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
{
		sockfd: socket函数正确返回的套接字
		addr:    标准地址信息结构体.(用户使用的结构体都要强转为该结构体)
		addrlen: 地址信息结构体长度. sizeof(seraddr).
		
		返回值: 成功返回 0
				 错误返回 -1.
	}
	端口:标识进程 系统占用:0 ~ 1023 
				   用户一般可使用 1024 ~ 65535
	ifconfig:查看网络信息.

3.监听listen() -----> 等待电话

int listen(int sockfd, int backlog);
{
		作用:监听客户端.
		sockfd: socket正确返回的套接字.
		backlog: 同一时间能够监听的客户端个数.(不是代表同时能连多少个客户端)
		返回值:成功返回 0
				错误返回 -1. 
}

4.接受客户端请求 accept(). ----> 接听电话

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
			
{
作用:接受客户端请求
	sockfd: socket函数正确返回的套接字
	addr:    获取客户端地址信息. 如果不需要则传 NULL.
	addrlen: 客户端地址信息长度. 如果不需要则传 NULL.
	
返回值: 正确返回一个通信套接字(连接套接字). 重点!
			 也是一个文件描述符.
		     与客户端的通信用通信套接字来实现.socket返回的值可以称为监听套接字
			 错误 -1
}

5.数据的收发. 因为是文件描述符所以 —> 通话过程

read()/write().可以表示收发
send()/recv() 也表示收发.  一定要配套使用.

6.关闭套接字. close(). ----> 挂电话
}

创建TCP服务器示例代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#define err_log(log) do{perror(log);exit(-1);}while(0)

int main(int argc, char *argv[])
{
	/*1.创建套接字*/
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		err_log("socket");
	}

	printf("socket-----success\n");

	/*2.绑定套接字*/
	struct sockaddr_in seraddr;
	memset(&seraddr, 0, sizeof(seraddr));
	seraddr.sin_family = AF_INET;   //ipv4
	seraddr.sin_port = htons(8888); //设置端口
	seraddr.sin_addr.s_addr = inet_addr("123.123.12.123"); // inet_addr把ip点分十进制转为二进制形式.   ip地址要是本机能存在的.

	if(bind(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr)) == -1)
	{
		err_log("bind");
	}

	printf("bind----\n");

	/*监听*/

	if(listen(sockfd, 5) == -1)
	{
		err_log("listen");
	}

	printf("listen----\n");

	/*接收请求*/

	int connfd = accept(sockfd, NULL, NULL);
	if(connfd == -1)
	{
		err_log("accept");
	}

	printf("accept-----\n");

	/*数据收发*/
	char buf[32] = {0};
	recv(connfd, buf, sizeof(buf),0); //read(connfd, buf, sizeof(buf));

	printf("recv=%s\n", buf);
	
	send(connfd, "ok", 3, 0);  //write(connfd, "sb", 3);

	/*关闭套接字*/

	close(connfd);
	close(sockfd);

	return 0;
}

PS:

端口:系统占用 0~1023   用户一般使用 1024 ~ 65535
字节序转换:
	htons(): 主机字节序转网络字节序(以short类型转换)
	ntohs(): 网络字节序转主机字节序(以short类型转换)
	htonl(): 主机字节序转网络字节序(以long类型转换)
	ntohl(): 网络字节序转主机字节序(以long类型转换)
ip地址转换函数:
	in_addr_t inet_addr(const char *cp); 点分十进制转为地址二进制形式
	char *inet_ntoa(struct in_addr in);  地址二进制形式转为点分十进制.

五、TCP客户端编写流程

{
	1.创建套接字--socket()
	2.设置服务器地址信息 struct sockaddr_in.
	3.主动请求连接服务器  connect().
	4.数据的收发. recv()/send()
	5.关闭套接字 close().
}
ping:        测试主机网络是否连通.   用法: ping + ip
ifconfig:    查看当前主机的网络信息.
netstat -na: 查看系统网络连接状态. 通常需要与 grep一起使用
			 netstat -na | grep "port或ip"

客户端示例代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#define PORT 8888
#define err_log(log) do{perror(log);exit(-1);}while(0)

int main(int argc, char *argv[])
{
	if(argc != 3)
	{
		fprintf(stderr, "Usage:%s <ip> <port>\n", argv[0]);
		return -1;
	}
	/*1.创建套接字*/
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		err_log("socket");
	}

	printf("socket-----success\n");

	/*2.设置服务器地址信息*/
	struct sockaddr_in seraddr;
	memset(&seraddr, 0, sizeof(seraddr));
	seraddr.sin_family = AF_INET;   //ipv4
	seraddr.sin_port = htons(atoi(argv[2])); //设置端口
	//seraddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // inet_addr把ip点分十进制转为二进制形式.   ip地址要是本机能存在的.
	seraddr.sin_addr.s_addr = inet_addr(argv[1]);

	/*3.请求连接*/
	if(connect(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr)) == -1)
	{
		err_log("connect");
	}

	printf("connect success\n");

	char buf[32] = {0};
	int ret = 0;
	while(1)
	{
		/*4.数据收发*/
		memset(buf, 0, sizeof(buf));
		printf("send msg:");
		fgets(buf, sizeof(buf), stdin);
		buf[strlen(buf)-1] = '\0';

		if(strcmp(buf, "quit") == 0)
		{
			printf("退出连接--------\n");
			break;
		}

		ret = send(sockfd, buf, sizeof(buf),0);
		if(ret == 0)
		{
			printf("client close\n");
			break;
		}

	}
	/*5.关闭套接字*/
	close(sockfd);

	return 0;
}

总结

本文的系统环境为Ubuntu,以上编程传输仅限于局域网的不同机器或者同一机器。按以上的流程就能写简单的TCP服务器,也可以自己动手试试写简单的文件服务器。可以在这基础上加更多东西,linux下一切皆文件,只要你设置好带宽,处理好数据收发,就应该什么都能传。
暂时先写简单的TCP客户端和服务器,后续文章有UDP协议的服务器和客户端,以及并发服务器;

上一篇:肥猫学习日记------------------Linux下的简单UDP协议建立


下一篇:Linux网络编程——广播、多播