UNIX 网络编程

目录

1 socket网络编程步骤

1.1 服务器编程步骤

1.2 客户端编程步骤

1.3 函数以及结构解释

2 基于TCP的一对多网络编程

2.1 服务器编程步骤

2.2 客户端编程

2.3 函数解释

2.4 基于TCP的网络聊天室

3 基于UDP的一对多编程

3.1 服务器编程步骤

3.2 客户端编程步骤

3.3 UDP下的读写数据

3.4 函数解释

3.5 基于UDP的时间服务器代码


本文要讲解Unix下的网络编程,即socket编程,也称为套接字编程。

Unix系统在网络上功能十分强大,历史悠久,因此有一个非常固定的套路,代码比较僵化,没有改变的余地。Socket编程主要包括两个方面,一个是本地通信,即计算机内部的进程间的通信(IPC),这个本文不过多讲解,主要集中在后面的内容,网络通信。

本文先介绍Unix网络编程的基本框架,以及具体的函数用法、结构体,然后分别介绍TCP网络编程和UDP网络编程。本文只介绍编程框架,而不会涉及具体的协议。

1 socket网络编程步骤

网络编程要考虑两个方面:服务器端和客户端,客户端通过网络与服务器端通信。

1.1 服务器编程步骤

1、调用函数socket(),用来创建socket描述符。

    int fd=socket(AF_INET,SOCK_DGRAM,0);

    if(fd==-1)

        perror("socket"),exit(-1);

 

2、准备通讯地址(三个结构体),进行数据交互。

struct sockaddr_in addr;

addr.sin_family=AF_INET;

addr.sin_port=htons(2222);//服务器的端口

addr.sin_addr.s_addr=inet_addr("192.168.1.112");//服务器的ip地址

 

3、绑定通讯地址和socket描述符,函数bind()

    int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));

    if(res==-1)

        perror("bind "),exit(-1);

    else

        printf("绑定成功\n");

 

4、读写描述符,和读写文件描述符一样,函数read()/write()。

    char buf[100]={};

    res=read(fd,buf,sizeof(buf));

 

5、使用函数close()关闭socket描述符。

close(fd);

1.2 客户端编程步骤

1、调用函数socket(),用来创建socket描述符。

    int fd=socket(AF_INET,SOCK_DGRAM,0);

    if(fd==-1)

        perror("socket "),exit(-1);

 

2、准备通讯地址(三个结构体),进行数据交互。

    struct sockaddr_in addr;

    addr.sin_family=AF_INET;

    addr.sin_port=htons(2222);//服务器

    addr.sin_addr.s_addr=inet_addr("192.168.1.112");

 

3、绑定通讯地址和socket描述符,函数connect()

    int res=connect(fd,(struct sockaddr*)&addr,sizeof(addr));

    if(res==-1)

        perror("connect "),exit(-1);

 

4、读写描述符,和读写文件描述符一样,函数read()/write()。

  write(fd,"hello",5);

5、使用函数close()关闭socket描述符。

    close(fd);

除了1.3的bind()换成connect()之外,其他的与服务器端一样,而且connect()和bind()用法完全一样。

1.3 函数以及结构解释

1、socket() 函数

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

参数解释

domin用于选择协议簇,可以选择一下的宏:

AF_UNIX/AF_LOCAL/AF_FILE :本地通信

AF_INET : 网络通信IPv4 (一般使用这个)

AF_INET6 :网络通信IPv6

type 用于选择通信的类型,主要包括:

SOCK_STREAM :数据流,用于TCP。

SOCK_DGRAM :数据报,用于UDP。

protocol参数本来应该指定协议,但是由于协议已经被前两个参数确定了,所以直接将protocol给0即可,这是Unix网络编程的僵化的体现。

如果函数执行成功,返回socket描述符,失败返回-1.

这个函数用来生成一个socket描述符。

2、结构体

网络通信需要三个结构体来指定网络信息,分别是struct sockaddr、struct sockaddr_un、struct sockaddr_in。其中sockaddr主要用于做函数的参数,并不储存数据,即没啥实际的用处,socjaddr_un负责存储本地通信的地址数据,sockaddr_in负责存储网络通信的地址数据。

#include 

struct sockaddr_un{

int sun_family; //用于指定协议簇,和socket()要保持一致

char sun_path[]; //存socket文件名,作为交互的媒介

}

#include

struct sockaddr_in{

int sin_family; //用于指定协议簇,和socket()一致

short sin_port; //端口号

struct in_addr sin_addr; //存储IP地址的结构

}

 

注:sockaddr_in / sockaddr_un 在做参数时必须类型转换为sockaddr,读写数据时,一方读数据,另一方必须写数据。

 

3、bind()

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

参数解释

sockfd是socket文件描述符,即socket()函数的返回值。

addr是通信地址的指针,需要做类型转换

size是通信地址的大小,即sizeof(struct)。

这个函数用来绑定自己的IP地址和端口号

4、connect()

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

参数解释

sockfd是socket文件描述符,即socket()函数的返回值。

addr是通信地址的指针,需要做类型转换

size是通信地址的大小,即sizeof(struct)。

TCP客户通过connect函数与服务端进行通信。

2 基于TCP的一对多网络编程

所谓的一对多编程,即只有一个服务器,有很多个客户端,现在基本都是一对多编程。TCP是一个基于连接的协议,在网络交互中,服务器和客户端要保持连接,不能断开。如果出现数据错误,TCP会重新发送,保证数据的正确和完整,但是资源的消耗比较大。

2.1 服务器编程步骤

1、socket()得到一个socket描述符

2、准备通信地址 struct sockaddr_in

3、绑定bind(),连接端口

4、监听函数listen(),这个函数用来控制同一时刻连接的人数。

5、等待客户端的连接,函数accept(),返回一个新的socket描述符,这个新的socket描述符用于读写交互。

6、读写函数read()/write()

7、关闭socket()

2.2 客户端编程

1、调用函数socket(),用来创建socket描述符。

2、准备通讯地址(三个结构体),进行数据交互。

3、绑定通讯地址和socket描述符,函数connect()

4、读写描述符,和读写文件描述符一样,函数read()/write()。

5、使用函数close()关闭socket描述符。

这里的步骤和上面的客户端编程的步骤是一样的

2.3 函数解释

1、listen()

该函数主要用于设置当有多个用户请求时,放入等待队列,等待队列的最大长度就是listen设置的。

int listen( int sockfd , int backlog);

参数解释

sockfd 是你要设置的socket描述符

backlog是队列的最大长度

2、accept()

int accept(int fd , struct sockaddr *addr , socklen_t *len);

参数解释

fd就是socket描述符

addr是一个结构体指针,用于传出客户端的通信地址

len是一个传入传出参数,传入addr的真实长度,传出接收到的客户端的通信地址的真实长度。(一般是相同的)

返回:成功返回新的socket描述符,失败返回-1.

2.4 基于TCP的网络聊天室

1、服务端代码

#include
#include
#include
#include
#include
#include
#include
#include

int sockall[100]; //存放socket描述符的数组
int sum;          //聊天室人数

int socket_findsit(){
	for(int i=0;i<100;i++)
		if(!sockall[i])
			return i;
	return -1;
}

void* task_chat(void* p){
	int index=(int)p;
	int fd=sockall[index];
	char buf[100]={};
	char name[100]={};
	char news[200]={};
	int i=0;

	sum++;
	printf("聊天室人数:%d\n",sum);
	/*读取用户的用户名*/
	int res=read(fd,name,sizeof(name));
	printf("用户%s进入聊天室\n",name);
	/*进入聊天环节*/
	while(1){
		res=read(fd,buf,sizeof(buf));
		if(res==0)
			break;
		if(!strcmp(buf,"bye")){
			printf("用户%s退出聊天室\n",name);
			break;
		}
		sprintf(news,"%.*s:%.*s",strlen(name),name,strlen(buf),buf);
		
		for(i=0;i<100;i++){
			if(sockall[i])
				write(sockall[i],news,strlen(news));
		}
		memset(buf,0,strlen(buf));
		memset(news,0,strlen(news));
	}
	close(fd);
	sockall[index]=0;
	sum--;
	printf("聊天室人数:%d\n",sum);
	return NULL;
}


int main(){
	pthread_t id;
	int fd=socket(AF_INET,SOCK_STREAM,0);//TCP协议
	if(fd==-1)
		perror("socket "),exit(-1);

	struct sockaddr_in addr;
	addr.sin_family=AF_INET; //协议簇
	addr.sin_port=htons(2222);
	addr.sin_addr.s_addr=inet_addr("192.168.1.112");
	/*防止复用*/
	int reuse=1;
	setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));

	int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
	if(res==-1)
		perror("bind "),exit(-1);
	printf("成功绑定端口\n");

	listen(fd,100);//设置等待队列的长度
	int index;
	while(1){
		struct sockaddr_in from;
		socklen_t len=sizeof(from);
		int sockfd=accept(fd,(struct sockaddr*)&from,&len);
		index=socket_findsit();
		if(index!=-1){
			sockall[index]=sockfd;
			pthread_create(&id,0,task_chat,(void*)(index));
		}
		else{
			printf("聊天室人数已满\n");
		}
	}

}

 

 

2、客户端代码

#include
#include
#include
#include
#include
#include
#include
#include
void* task_read(void*p){
	int fd=(int)p;
	char buf[200]={};
	while(1){
		read(fd,buf,sizeof(buf));
		printf("%s\n",buf);
		memset(buf,0,strlen(buf));
	}
}


int main(){
	pthread_t id;
	int fd=socket(AF_INET,SOCK_STREAM,0);
	if(fd==-1)
		perror("socket "),exit(-1);

	struct sockaddr_in addr;
	addr.sin_family=AF_INET;
	addr.sin_port=htons(2222);
	addr.sin_addr.s_addr=inet_addr("114.116.5.220");

	int res=connect(fd,(struct sockaddr*)&addr,sizeof(addr));
	if(res==-1)
		perror("connect "),exit(-1);
	printf("成功连接服务器!\n");

	char buf1[100]={};
	char buf2[100]={};

	printf("请输入您的用户名:\n");
	scanf("%s",buf1);
	write(fd,buf1,strlen(buf1));
	memset(buf1,0,sizeof(buf1));
	printf("欢迎来到聊天室!\n");
	
	pthread_create(&id,0,task_read,(void*)fd);
	while(1){
		scanf("%s",buf1);
		write(fd,buf1,strlen(buf1));
		if(!strcmp(buf1,"bye"))
			break;
		memset(buf1,0,strlen(buf1));
	}
	close(fd);
}

 

 

3 基于UDP的一对多编程

UDP协议,用户数据报协议,无需连接,消耗资源少,但是有可能出错。

3.1 服务器编程步骤

1、socket(),得到socket描述符

2、准备通信地址,strutc sockaddr_in

3、绑定bind()

4、读写

5、关闭描述符

 

3.2 客户端编程步骤

1、socket(),得到socket描述符

2、准备通信地址,strutc sockaddr_in

3、读写

4、关闭描述符

客户端不需要绑定或者连接。

 

3.3 UDP下的读写数据

UDP协议下,使用的读写函数与TCP不同,在不连接的前提下,读数据使用read()或者recvfrom(),read()只能读数据,不能读对方发送方的通信地址,而recvfrom两者皆可。写数据使用sendto(),不能使用write()。

 

3.4 函数解释

1、recvfrom()

size_t recvfrom( int fd , void *buf , size_t len , int flags , struct sockaddr *src_addr , socklen_t *addrlen);

参数解释

fd 文件描述符,在这里就是socket描述符,buf是要将文件读取进的对象,len是读取的长度,flags一般给0即可,src_addr是一个传出参数,用于接收发送方的通信地址,addrlen是一个传入传出参数,传入addr的真实长度,传出接收到的客户端的通信地址的真实长度。

2、sendto()

int sendto(int fd , void *addr , size_t len , int flag , struct sockaddr *addr , socklen_t addlen);

参数解释

fd是文件描述符,addr是要写入的内容指针,len是要写入的长度,addr 传入数据接收方的通信地址,addrlen就是通信地址的长度。

返回:成功返回发送的字节数,失败返回-1。

 

3.5 基于UDP的时间服务器代码

1、服务器代码

#include
#include
#include
#include
#include
#include
#include
#include

int main(){
	int fd=socket(AF_INET,SOCK_DGRAM,0);
	if(fd==-1)
		perror("socket "),exit(-1);
	struct sockaddr_in addr;
	addr.sin_family=AF_INET;
	addr.sin_port=htons(2222);
	addr.sin_addr.s_addr=inet_addr("192.168.1.112");

	int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
	if(res==-1)
		perror("bind "),exit(-1);
	printf("bind ok\n");

	char buf[100]={};
	while(1){
		struct sockaddr_in from;
		socklen_t len=sizeof(from);

		res=recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&from,&len);
		
		pid_t pid=fork();
		if(!pid){
			time_t time1=time(NULL);
			struct tm* tm1=localtime(&time1);
			printf("读到了%d字节,内容:%s\n",res,buf);
			memset(buf,0,sizeof(buf));
			sprintf(buf,"%d/%d/%d  %d:%d:%d",tm1->tm_year+1900,tm1->tm_mon+1,tm1->tm_mday,tm1->tm_hour,tm1->tm_min,tm1->tm_sec);
			sendto(fd,buf,sizeof(buf),0,(struct sockaddr*)&from,len);
			exit(0);
		}
	}
}

 

 

2、客户端代码

#include
#include
#include
#include
#include
#include

int main(){
	int fd=socket(AF_INET,SOCK_DGRAM,0);
	if(fd==-1)
		perror("socket "),exit(-1);
	struct sockaddr_in addr;
	addr.sin_family=AF_INET;
	addr.sin_port=htons(2222);
	addr.sin_addr.s_addr=inet_addr("192.168.1.112");

	sendto(fd,"hello",5,0,(struct sockaddr*)&addr,sizeof(addr));
	char buf[100]={};
	int res=read(fd,buf,sizeof(buf));
	printf("日期:%s\n",buf);
	close(fd);
	return 0;
}

 

 

你可能感兴趣的:(UC)