liunx之socket网络编程

前言:socket网络编程是一门技术,它主要是在网络通信中经常用到。与进程间通信最大的区别是socket网络编程是基于多机信息传递的,而前者基于单机。

一、了解TCP和UDP的区别。

①TCP面向连接(如打电话要先拨号建立连接;UDP是无连接的,即发送数据之前不需要建立连接。
②TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
③TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
④每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
⑤TCP首部开销20字节;UDP的首部开销小,只有8个字节。
⑥TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。

二、socket编程使用的API

1.字节序转换API

uint16_t htons(uint16_t host16bitvalue);    //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue);    //返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue);     //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue);     //返回主机字节序的值

h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取

2.地址转换API

int inet_aton(const char * straddr,struct in_addr *addrp)
说明:把字符串形式的"192.168.1.123"转换为网络能识别的格式
第一个参数:IP地址的字符串。
第二个参数:字符串的地址

char *inet_ntoa(struct in_addr inaddr);
把网络格式的ip地址转为字符串形式

3.创建通信端点 socket

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

第一个参数为:指定一个通信域,通常使用AF_INET(IPv4 Internet protocols)

第二个参数为:socket的制定类型,通常使用SOCK_STREAM(提供有序的、可靠的、双向的、基于连接的字节流。可以采用带外数据传输机制支持。)

第三个参数为:参数protocol用来指明所要接收的协议包,通常写0用于接收任何的IP数据包。其中的校验和和协议分析由程序自己完成

返回值:如果成功,将返回新套接字的文件描述符。若失败则返回-1。

eg: socket(AF_INET,SOCK_STREAM,0);

4.将名称绑定到套接字 bind

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

第一个参数为:创建通信端点时返回的文件标识符。

第二个参数为:addr为结构体变量的指针,这里我们使用 sockaddr_in 结构体,然后再强制转换为 sockaddr 类型。

sockaddr_in 结构体
struct sockaddr_in{
	sa_family_t sin_family; //地址族(Address Family),也就是地址类型
	uint16_t sin_port; //16位的端口号
	struct in_addr sin_addr; //32位IP地址
	char sin_zero[8]; //不使用,一般用0填充
};

第三个参数为:addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。

返回值:如果成功,则返回0。出现错误时,返回-1,并设置errno适当。

eg:bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

5.监听套接字上的连接 listen

int listen(int sockfd, int backlog);

第一个参数为:创建通信端点时返回的文件标识符。

第二个参数为:backlog参数定义了待定队列的最大长度sockfd的连接可能会增加。如果连接请求到达时如果队列已满,客户端可能会收到一个指示为ECONNRE‐的错误融合或,如果底层协议支持重传,请求可以被忽略,以便以后重新尝试连接时成功。

返回值:如果成功,则返回0。出现错误时,返回-1,并设置errno适当。

eg:listen(s_fd,10);

6.接收套接字上的连接

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

第一个参数为:创建通信端点时返回的文件标识符。

第二个参数为:参数addr是指向sockaddr结构体的指针,需要转换类型。若为NULL,则addrlen也为空

第三个参数为:adr长度的地址.

返回值:如果成功,这些系统调用将返回一个非负整数a被接受套接字的描述符。错误时,返回-1,errn为适当的设置。

eg: int adr=sizeof(struct sockaddr_in);
    a_fd=accept(s_fd,(struct sockaddr *)&a_addr,&adr);

7.在套接字上发起连接 connect

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

第一个参数为:创建通信端点时返回的文件标识符。

第二个参数为:addr为要访问的地址。

第三个参数为:要访问地址的长度。

返回值:如果成功,则返回0。出现错误时,返回-1,并设置errno适当。

三、服务器端的构建(双人)

①创建通信端点套接字
②将名称绑定到套接字(IP地址和端口号)
③监听连接
④接收套接字的连接
⑤数据交互(读取、发送)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc,char **argv)
{
        int s_fd;
        int a_fd;
        int n_read;
        char readbuff[128];
        char msg[128]={0};
        struct sockaddr_in s_addr;
        struct sockaddr_in a_addr;
        
	if(argc!=3){
		printf("progrm is not good\n");
		exit(-1);
	}
	
	memset(&s_addr,0,sizeof(struct sockaddr_in));   //reset
        memset(&a_addr,0,sizeof(struct sockaddr_in));   //reset


        //1.socket
        s_fd=socket(AF_INET,SOCK_STREAM,0);
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }

        //2.bind
        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(atoi(argv[2]));
        //s_addr.sin_addr.s_addr=("127.0.0.1");
        inet_aton(argv[1],&s_addr.sin_addr);
        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

        //3.listen
        listen(s_fd,10);

        //4.accept
        int adr=sizeof(struct sockaddr_in);
	while(1){
        	a_fd=accept(s_fd,(struct sockaddr *)&a_addr,&adr);
        	if(a_fd==-1){
                	perror("accept");
        	}
        	printf("get connect:%s\n",inet_ntoa(a_addr.sin_addr));
        	
		if(fork()==0){
			if(fork()==0){
				while(1){
					//write
                        		memset(msg,0,sizeof(msg));
					printf("input: ");
					gets(msg);
					write(a_fd,msg,strlen(msg));
				}
			}
			//read
			while(1){
        			memset(readbuff,0,sizeof(readbuff));
				n_read=read(a_fd,readbuff,128);	
        			if(n_read==-1){
                			perror("read");
        			}else{
        				printf("get message from client:%d,%s\n",n_read,readbuff);
        		        }
			}
			break;	
		}
	}
        return 0;
}

四、接收端的构建(双人)

①创建通信端点
②发起连接
③发送数据
④读取数据

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc,char **argv)
{
        int c_fd;
        int n_read;
        char readbuff[128];
        char msg[128]={0};
        struct sockaddr_in c_addr;
       
	if(argc!=3){
                printf("progrm is not good\n");
                exit(-1);
        }

	memset(&c_addr,0,sizeof(struct sockaddr_in));   //reset


        //1.socket
        c_fd=socket(AF_INET,SOCK_STREAM,0);
        if(c_fd == -1){
                perror("socket:\n");
                exit(-1);
        }

	 //2.connect
        c_addr.sin_family=AF_INET;
        c_addr.sin_port=htons(atoi(argv[2]));
        inet_aton(argv[1],&c_addr.sin_addr);

        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))==-1){
                perror("connect\n");
                exit(-1);
        }
	while(1){
		if(fork()==0){
			while(1){
				memset(msg,0,sizeof(msg));
				printf("input: ");
				gets(msg);
				//3.write
       				write(c_fd,msg,strlen(msg));
			}
		}		
		while(1){	
		 	//3.read
			memset(readbuff,0,sizeof(readbuff));
       		 	n_read=read(c_fd,readbuff,128);
       		 	if(n_read==-1){
       				printf("perror:\n");
       		 	}else{
               	 		printf("I get message from server:%d,context:%s\n",n_read,readbuff);
       		 	}
		}
	}
        return 0;
}

进行多机通讯时只需要修改服务器端
①定义mark来标识用户端号数
②更改accept

//4.accept
        int adr=sizeof(struct sockaddr_in);
        while(1){
                a_fd=accept(s_fd,(struct sockaddr *)&a_addr,&adr);
                if(a_fd==-1){
                        perror("accept");
                }
                mark++;
                printf("get connect:%s\n",inet_ntoa(a_addr.sin_addr));

                if(fork()==0){
                        if(fork()==0){
                                while(1){
                                        //write
                                        sprintf(msg,"Welcom No.%d client",mark); //当前用户端号数
                                        write(a_fd,msg,strlen(msg));
                                        sleep(3);
                                }
                        }
                        //read
                        while(1){
                                memset(readbuff,0,sizeof(readbuff));
                                n_read=read(a_fd,readbuff,128);
                                if(n_read==-1){
                                        perror("read");
                                }else{
                                        printf("get message from client:%d,%s\n",n_read,readbuff);
                                }
                        }
                        break;
                }
        }
上一篇:error C2375: “WSACleanup”: 重定义 C2011 “sockaddr_in”:“struct”类型重定义


下一篇:C语言网络编程——TCP