Bootstrap

linux---socket编程(网络)

计算机网络概述

Linux系统也与网络密不可分,无论是服务器开发,还是嵌入式应用等领域,都需要通过网络进行数据传递。Linux网络编程一般通过socket(套接字)接口实现。

关于网络的概述我就不细讲了,看了晕头转向的,在此推荐这个老哥的博文
1-1:网络初识之了解什么是协议以及TCP/IP协议
1-2:网络初识之了解OSI和TCP/IP及网络分层(物理层,数据链路层,网路层,传输层,应用层)
1-3:网络初识之网络传输的基本流程TCP首部,IP首部,MAC地址,IP地址等

补充几点:
网络结构模式:C/S B/S
网络结构模式分为两种,一种为客户机(client)/服务器(server)模式,即C/S模式,此种模式需要在进行通信的两端分别架设客户机和服务器;另一种为浏览器(browser)/服务器模式,即B/S模式,是WEB(World Wide Web)兴起后的一种网络结构模式,客户机只需安装浏览器,便可与服务器进行交互。

socket编程基础

在Linux系统中,socket可用于表示进程间进行网络通信时使用的特殊文件类型,也可用于表示socket编程中的一系列接口。socket本意为“插座”,常被称为套接字。当使用socket进行通信时,进程会先生成一个socket文件,之后再通过socket文件进行数据传递。

Linux系统中将socket具体化为一种文件只是为了给用户提供与操作普通文件相同的接口,使用户可以通过文件描述符来引用和操作套接字。实际上,socket的本质为内存缓冲区形成的伪文件,与管道本质类似,不同的是,socket多用于与网络相关的进程通信。

在这里插入图片描述
在网络通信中,socket一定是成对出现的。socket的缓冲区分为读写两个部分,每个socket都能接收和发送文件,一端的发送缓冲区会对应另一端的接收缓冲区。

对用户来说,不必了解socket文件的具体构成,只需掌握与socket相关的接口即可。socket接口位于应用层与TCP/IP协议族之间,是基于软件的抽象层,它与体系结构中各层的关系如图10-5所示。

在这里插入图片描述

socket编程接口

Linux系统中常用的socket网络编程接口有socket()、bind()、listen()、accept()、connect()、send()、recv()、close(),其中connect()与send()为客户端专用接口bind()、listen()、accept()及recv()为服务器端专用接口socket()与close()则由服务器与客户端共用。

socket()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

bind()

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
如亦可使用以下语句,定义一个struct sockaddr_in类型的结构体:

struct sockaddr_in servaddr;			//结构体定义
bzero(&servaddr, sizeof(servaddr));		//结构体清零
servaddr.sin_family = AF_INET;			//设置地址类型为AF_INET
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);	//设置网络地址为INADDR_ANY
servaddr.sin_port = htons(85);			//设置端口号为85

在这里插入图片描述

listen()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

accept()

在这里插入图片描述

connect()

在这里插入图片描述

send()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

recv()

在这里插入图片描述

close()

在这里插入图片描述

socket 通信流程

根据进程在网络通信中使用的协议,可将socket通信方式分为两种:一种是面向连接、基于TCP协议的通信;另一种是面向无连接、基于UDP协议的通信。

在这里插入图片描述

网络编程相关知识

网络字节序—大小端

在这里插入图片描述

Linux系统中提供了一些用于字节序转换的函数,这些函数存在于函数库arpa/inet.h中,它们的定义如下:

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

ip转换函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

sockaddr数据结构

在这里插入图片描述
在这里插入图片描述

更加详细看:2-1:套接字(Socket)编程之必备知识

socket 网络通信实例

C/S TCP

在这里插入图片描述
在这里插入图片描述
服务端程序:tcpserver.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80							//最大数据长度
#define SERV_PORT 6666						//服务器端口号
int main(void)
{
	struct sockaddr_in servaddr, cliaddr; 	//定义服务器与客户端地址结构体
	socklen_t cliaddr_len; 					//客户端地址长度
	int listenfd, connfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;
	//创建服务器端套接字文件
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	//初始化服务器端口地址
	bzero(&servaddr, sizeof(servaddr));	//将服务器端口地址清零
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);
	//将套接字文件与服务器端口地址绑定
	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	//监听,并设置最大连接数为20
	listen(listenfd, 20);
	printf("Accepting connections ...\n");
	//接收客户端数据,并处理请求
	while (1) {
		cliaddr_len = sizeof(cliaddr);
		connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
		n = recv(connfd, buf, MAXLINE, 0);
		printf("received from %s at PORT %d\n",
			inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
			ntohs(cliaddr.sin_port));
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);
		send(connfd, buf, n, 0);
		//关闭连接
		close(connfd);
	}
	return 0;
}

tcpclient.c 客户端程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;		//定义服务器地址结构体
	char buf[MAXLINE];
	int sockfd, n;
	char *str;
	if (argc != 2) {
		fputs("usage: ./client message\n", stderr);
		exit(1);
	}
	str = argv[1];
	//创建客户端套接字文件
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	//初始化服务器端口地址
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);
	//请求链接
	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	//发送数据
	send(sockfd, str, strlen(str), 0);
	//接收客户端返回的数据
	n = recv(sockfd, buf, MAXLINE, 0);
	printf("Response from server:\n");
	//将客户端返回的数据打印到终端
	write(STDOUT_FILENO, buf, n);
	//关闭连接
	close(sockfd);
	return 0;
}

在这里插入图片描述

C/S UDP

在这里插入图片描述
在这里插入图片描述
udpserver.c //服务器端

#include <string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <ctype.h>
#define MAXLINE 80							//最大数据长度
#define SERV_PORT 6666						//服务器端口号
int main(void)
{
	struct sockaddr_in servaddr, cliaddr;	//定义服务器与客户端地址结构体
	socklen_t cliaddr_len;					//客户端地址长度
	int sockfd;								//服务器socket文件描述符
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);//创建服务器端套接字文件
	//初始化服务器端口地址
	bzero(&servaddr, sizeof(servaddr));	//地址结构体清零
	servaddr.sin_family = AF_INET;			//指定协议族
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);	//指定端口号
	//绑定服务器端口地址
	bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	printf("Accepting connections ...\n");
	//数据传输
	while (1) {
		cliaddr_len = sizeof(cliaddr);
		//接收数据
		n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr*)&cliaddr,
			&cliaddr_len);
		if (n == -1)
			perror("recvfrom error");
		printf("received from %s at PORT %d\n",
			inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
			ntohs(cliaddr.sin_port));
		//服务器端操作,小写转大写
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);
		n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr,
			sizeof(cliaddr));
		if (n == -1)
			perror("sendto error");
	}
	close(sockfd);
	return 0;
}

udpclient.c //客户端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	int sockfd, n;
	char buf[MAXLINE];
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);
	//发送数据到客户端
	while (fgets(buf, MAXLINE, stdin) != NULL) {
		n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr,
			sizeof(servaddr));
		if (n == -1)
			perror("sendto error");
		//接收客户端返回的数据
		n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
		if (n == -1)
			perror("recvfrom error");
		//将接收到的数据打印到终端
		send(STDOUT_FILENO, buf, n, 0);
	}
	close(sockfd);
	return 0;
}

在这里插入图片描述

2-3:套接字(Socket)编程之UDP通信

2-4:套接字(Socket)编程之TCP通信

socket本地通信

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
服务器端 dmserve.c

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#define QLEN 10
//创建服务器进程,成功返回0,出错返回小于0的errno
int serv_listen(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;
	//创建本地domain套接字
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-1);
	//删除套接字文件,避免因文件存在导致bind()绑定失败	
	unlink(name);
	//初始化套接字结构体地址
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -2;
		goto errout;
	}
	if (listen(fd, QLEN) < 0) { //告知内核这是一个服务器进程
		rval = -3;
		goto errout;
	}
	return(fd);
errout:
	err = errno;
	close(fd);
	errno = err;
	return(rval);
}
int serv_accept(int listenfd, uid_t *uidptr)
{
	int clifd, len, err, rval;
	time_t staletime;
	struct sockaddr_un un;
	struct stat statbuf;
	len = sizeof(un);
	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
		return(-1);
	//从调用地址获取客户端的uid
	len -= offsetof(struct sockaddr_un, sun_path); //获取路径名长度
	un.sun_path[len] = 0; 	//为路径名字符串添加终止符
	if (stat(un.sun_path, &statbuf) < 0) {
		rval = -2;
		goto errout;
	}
	if (S_ISSOCK(statbuf.st_mode) == 0) {
		rval = -3; 				//若返回值为-3,说明这不是一个socket文件
		goto errout;
	}
	if (uidptr != NULL)
		*uidptr = statbuf.st_uid; 		//返回uid的调用者指针
	//到此成功获取路径名
	unlink(un.sun_path);
	return(clifd);
errout:
	err = errno;
	close(clifd);
	errno = err;
	return(rval);
}
int main(void)
{
	int lfd, cfd, n, i;
	uid_t cuid;
	char buf[1024];
	lfd = serv_listen("foo.socket");
	if (lfd < 0) {
		switch (lfd) {
		case -3:perror("listen"); break;
		case -2:perror("bind"); break;
		case -1:perror("socket"); break;
		}
		exit(-1);
	}
	cfd = serv_accept(lfd, &cuid);
	if (cfd < 0) {
		switch (cfd) {
		case -3:perror("not a socket"); break;
		case -2:perror("a bad filename"); break;
		case -1:perror("accept"); break;
		}
		exit(-1);
	}
	while (1) {
	r_again:
		n = read(cfd, buf, 1024);
		if (n == -1) {
			if (errno == EINTR)
				goto r_again;
		}
		else if (n == 0) {
			printf("the other side has been closed.\n");
			break;
		}
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);
		write(cfd, buf, n);
	}
	close(cfd);
	close(lfd);
	return 0;
}

客户端 dmclient.c

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
//创建客户端进程,成功返回0,出错返回小于0的errno
int cli_conn(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;
	//创建本地套接字domain
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-1);
	//使用自定义地址填充socket地址结构体
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
	len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
	unlink(un.sun_path); //避免因文件已存在导致的bind()失败
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -2;
		goto errout;
	}
	//使用服务器进程地址填充socket地址结构体
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	if (connect(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -4;
		goto errout;
	}
	return(fd);
errout:
	err = errno;
	close(fd);
	errno = err;
	return(rval);
}
int main(void)
{
	int fd, n;
	char buf[1024];
	fd = cli_conn("foo.socket");		//套接字文件为foo.socket
	if (fd < 0) {						//容错处理
		switch (fd) {
		case -4:perror("connect"); break;
		case -3:perror("listen"); break;
		case -2:perror("bind"); break;
		case -1:perror("socket"); break;
		}
		exit(-1);
	}
	while (fgets(buf, sizeof(buf), stdin) != NULL) {
		write(fd, buf, strlen(buf));
		n = read(fd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, n);
	}
	close(fd);
	return 0;
}

在这里插入图片描述

脚下留心:出错处理函数封装

在这里插入图片描述
面将对网络编程中的常用接read()write()等函数进行再次封装,为其添加出错处理功能,并将这些新函数的声明和定义分别保存在文件wrap.h 和 wrap.c

在这里插入图片描述

#ifndef __WRAP_H_
#define __WRAP_H_
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
void Bind(int fd, const struct sockaddr *sa, socklen_t salen);
void Connect(int fd, const struct sockaddr *sa, socklen_t salen);
void Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
void Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
static ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif

在这里插入图片描述

#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
//socket()
int Socket(int family, int type, int protocol)
{
	int n;
	if ((n = socket(family, type, protocol)) < 0)//若socket调用失败
		perr_exit("socket error");
	return n;
}
//bind()
void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
	if (bind(fd, sa, salen) < 0)				//若bind()调用失败
		perr_exit("bind error");
}
//listen()
void Listen(int fd, int backlog)
{
	if (listen(fd, backlog) < 0)				//若listen()调用失败
		perr_exit("listen error");
}
//connect()
void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
	if (connect(fd, sa, salen) < 0)				//若connect()调用失败
		perr_exit("connect error");
}
//accept()
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;
again:
	if ((n = accept(fd, sa, salenptr)) < 0) {	//若accept()调用失败
		if ((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}
//close()
void Close(int fd)
{
	if (close(fd) == -1)						//关闭socket失败
		perr_exit("close error");
}
//read()
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;
again:
	if ((n = read(fd, ptr, nbytes)) == -1) {	//读取数据失败
		if (errno == EINTR)
			goto again;							//重读fd
		else
			return -1;
	}
	return n;
}
//write()
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;
again:
	if ((n = write(fd, ptr, nbytes)) == -1) {	//写数据出错
		if (errno == EINTR)
			goto again;							//重新写入
		else
			return -1;
	}
	return n;
}
void perr_exit(const char *s)
{
	perror(s);
	exit(1);
}

;