Linux-套接字编程

认识套接字

认识IP地址

网路通信的本质是两个不同的主机在进行数据交互,在交互的过程中,数据是不会自己决定流向,这就需要有标示来指明数据从哪里来要到那里去。

IP地址在网络中标识了唯一的一台主机,只要获取到对方的IP地址,就可以给对方发送数据。

IP地址在IP协议中是用来标示网络中唯一的一台主机
IP协议不同,对应的IP地址也不同,对于IPv4来说,IP地址是一个4字节32位的整数
我们通常使用点分十进制来表示一个IP地址,例如192.168.0.1,每个用小数点分割开来的数字表示一个字节,范围是0-255

认识端口号

一台主机上面可能运行了好多个进程,当主机接收到数据OS就需要决定把这个数据交给哪个进程来进行解析,但是交给哪个进程来解析呢,OS无法自行决定,这就需要一个标识来说明这是给哪个进程的。这就是端口号。
IP地址标识了网络中的唯一一台主机,端口号标识了一台主机上的唯一一个进程

端口号是一个2字节16位的整数
端口号标识了一个进程,告诉OS这个数据要交给哪一个进程来处理
IP+端口号能标识网络上的唯一一台主机好的唯一一个进程
一个端口号只能被一个进程占用
一个进程可以绑定多个端口号,但是一个端口号只能被一个进程绑定

套接字可以简单理解为IP+端口号的组合。
网络上进行通信还需要一个五元组:通信协议+源IP+源端口+目的IP+目的端口

认识TCP和UDP

TCP:传输控制协议

传输层协议
有连接
面向字节流
可靠传输

UDP:用户数据报协议

传输层协议
无连接
面向数据报
不可靠传输

客户端/服务器模式

在网络应用中,两个通信的进程最常用的就是客户端服务器模式(C/S模式),即客户端向服务器发起请求,服务器接收到请求之后对客户端提供相应的服务。
服务器端

服务器先启动,通知本地主机,可以接收请求
等待客户端发起请求
接收到客户端请求,处理请求并发送应答信号
返回第二步,等待新的客户端连接
关闭服务器

客户端

打开通信通道,连接到服务器所在主机的特定端口
向服务器发起请求,等待接收并应答。
请求结束后关闭通信通道

套接字常见API

创建套接字

#include <sys/types.h>         
#include <sys/socket.h>

int socket(int domain, int type, int protocol); //客户端服务器

参数domain指明通信发生的区域,常用AF_INET
type指明建立套接字的类型:
TCP流式套接字(SOCK_STREAM)
UDP数据报式套接字(SOCK_DGRAM)
原始式套接字(SOCK_RAW)
protocol说明该套接字使用的特定协议,一般为0使用默认连接
socket根据这三个参数建立一个套接字并将响应资源分配给它,同时返回一个整形的套接字号
绑定端口号

#include <sys/types.h>          
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

当一个套接字创建出来后是没有被命名的,bind将套接字地址与所创建的套接字建立联系。
参数sockfd是创建的套接字号,addr是本地套接字
监听socket

#include <sys/types.h>          
#include <sys/socket.h>

int listen(int sockfd, int backlog); //TCP

sockfd是需要监听的套接字号
backlog是请求连接队列的最大长度

建立连接

#include <sys/types.h>         
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen); //TCP客户端

sockfd是想要建立连接的本地套接字描述符
addr是对方套接字地址结构的指针
接收请求

#include <sys/types.h>         
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//TCP服务器

sockfd是本地套接字描述符
在调用accpet之前应先调用listen
addr指向客户端套接字结构的指针

accept()用于面向连接服务器。参数addr和addrlen存放客户方的地址信息。调用前,参数addr 指向一个初始值为空的地址结构,而addrlen 的初始值为0;调用accept()后,服务器等待从编号为sockfd的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr 和addrlen,并创建一个与sockfd有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。

简单的TCP网络程序

//TCP-server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	if(argc != 3){
		printf("Usage : %s [ip] [port]\n", argv[0]);
		return -1;
	}
	
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if(listen_sock < 0){
		perror("socket");
		return 1;
	}
	
	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(atoi(argv[2]));
	local.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t len = sizeof(local);
	
	if(bind(listen_sock, (struct sockaddr*)&local, len) < 0){
		perror("bind");
		return 2;
	}
	
	if(listen(listen_sock, 5) < 0){
		perror("listen");
		return 3;
	}
	
	char buf[1024] = {0};
	while(1){
		struct sockaddr_in client;
		len = sizeof(client);
		int sock = accept(listen_sock, (struct sockaddr*)&client, &len);
		if(sock < 0){
			perror("accept");
			continue;
		}
		printf("get a new link : [%s] [%d]\n", \
					inet_ntoa(client.sin_addr), ntohs(client.sin_port));
		
		//recv data
		ssize_t s = recv(sock, buf, sizeof(buf)-1, 0);
		if(s < 0){
			perror("recv");
			close(sock);
			continue;
		}else if(s == 0){ //连接中断
			printf("perr shutdown\n");
			close(sock);
			continue;
		}
		buf[s] = 0;
		printf("[ip : port] : %s", inet_ntoa(client.sin_addr), \
				ntohs(client.sin_port), buf);
				
		//send data		
		memset(buf, 0x00, sizeof(buf));
		scanf("%s", buf);
		send(sock, buf, strlen(buf), 0);
	}
}

客户端程序:

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

int main(int argc, char *argv)
{
	if(argc != 3){
		printf("Usage : %s [ip] [port]\n", argv[0]);
		return -1;
	}
	
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0){
		perror("socket");
		return -2;
	} 
	
	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(atoi(argv[2]));
	local.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t len = sizeof(local);
	
	if(connect(sock, (struct sockaddr*)&local, len) < 0){
		perror("connect");
		return -3;
	}
	
	char buf[1024] = {0};
	while(1){
		printf("Please Enter#:")
		scanf("%s", buf);
		send(sock, buf, strlen(buf), 0);
		
		memset(buf, 0x00, sizeof(buf));
		ssize_t s = recv(sock, buf, sizeof(buf)-1, 0);
		if(s < 0){
			perror("recv");
			close(sock);
			continue;
		}
		else if(s == 0){
			printf("perr shutdown!\n");
			close(sock);
			continue;
		}
		buf[s] = 0;
		printf("server : %s\n", buf);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值