您好,欢迎来到爱问旅游网。
搜索
您的当前位置:首页网络编程——Socket(Linux套接字编程)

网络编程——Socket(Linux套接字编程)

来源:爱问旅游网

         作者:FaramitaSpends          邮箱:(部分内容摘自网络,如有问题请联系作者)

        套接字是一种网络API(应用程序编程接口)。它定义了许多函数和例程,程序员可以用它开发网络应用程序。套接字接口本意在于提供一种进程间通信的方法,使得在相同或不同主机上的进程能以相同的规范进行双向信息传送。

进程通过调用套接字接口API来实现相互之间的通信。套接字接口又利用下层的网络通信协议功能和系统调用实现实际的通信工作。

        Socket编程接口有以下两种:

              –  Windows Socket

              – Linux Socket(本文介绍内容)

         因此windows程序可以通过网络和Linux/UNIX计算机进行通信,实现客户/服务器系统,反之亦然。

一、概要介绍


二、Socket函数详解

 

 //Socket所用函数包含头文件

#include <sys/types.h>         //数据类型

#include<sys/socket.h>        //函数定义

 

1、socket()函数

 

//返回套接字描述符。返回值为非负描述字成功,-1失败

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

 

最常用的有以下两种:

-  type:指定套接字的通信类型。

流套接字:由类型SOCK_STREAM指定,基于TCP/IP实现,提供一个有序、可靠、双向字节流的连接,发送的数据不会丢失、乱序、重复。大的消息会被分块、传输、重组,很像一个文件流。

数据报套接字:由SOCK_DGRAM指定,基于UDP/IP协议,不建立和维持可靠连接,开销小。

-  protocol:指定使用的协议。不需要选择特定协议,只要默认值(0)即可。

 

2、bind()函数

 int bind( int  socket,const  struct  sockaddr* address, size_t  address_len);

 

structsockaddr_un{

      sa_family_tsun_family 

      charsun_path[];

}

structsockaddr_in{

      shortint           sin_family;

      unsignedshort int  sin_port;

      structin_addr      sin_addr;

}

 

补充:

structsockaddr{

};

但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构sockaddr_in (在netinet/in.h中定义):

structsockaddr_in{

  unsigned short int sin_port;/* 存储端口号 */

  unsigned char sin_zero[8];  /* 空字节 */

};

 

注1:

1)  sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。14=2+4+8

 structin_addr{  unsigned long s_addr;  };

3)  sin_port存储端口号(使用网络字节顺序),数据类型是一个16位的无符号整数类型;

4)  sin_family指代协议族,在TCP套接字编程中只能是AF_INET;

 

所以在使用bind之前应先初始化结构体各参数。

 

注2:

众所周知端口:1~1023,1~255之间为大部分众所周知端口,256~1023端口通常由UNIX占用

注册端口:1024~49151

动态或私有端口:49151~65535

 

3、listen()函数

int  listen( int  socket, int backlog);

套接字队列中,等待处理的进入连接的个数最多不能超过backlog这个数字,多出的连接请求将被拒绝,导致客户连接失败。

当服务器正忙于处理一个客户请求时,后续的客户连接放入队列等待处理。

函数执行成功返回0,失败返回-1。

 

4、accept()函数

int  accept( int  socket, struct  sockaddr* address, size_t* address_len);

返回值为非负描述字成功,-1失败

 

5、connect()函数

int  connect( int  socket, struct  sockaddr* address, size_t  address_len);

参数socket指定的套接字将连接到参数address指定的服务器套接字,address指向的结构长度由参数address_len指定。

函数执行成功返回0,失败返回-1。

 

6、send()函数(同write()函数)

int Send(int sockfd, const void * data, int data_len, unsigned intflags)

功能:

1)、在TCP连接上发送数据,返回成功传送数据的长度,出错时返回-1。

2)、send会将外发数据复制到OS内核中,也可以使用send发送面向连接的UDP报文。

参数说明:

sockfd:套接字描述符

data:指向要发送数据的指针

data_len:数据长度

flags:一直为0

 

7、sendto()函数

int Sendto(int sockfd, const void * data, int data_len, unsigned intflags, struct sockaddr *remaddr,int remaddr_len

功能:基于UDP发送数据报,返回实际发送的数据长度,出错时返回-1

参数说明:

sockfd:套接字描述符

data:指向要发送数据的指针

data_len:数据长度

flags:一直为0

 

8、recv()函数(同read()函数)

int recv(int sockfd, void *buf, int buf_len,unsigned int flags);

功能:

1)、从TCP接收数据,返回实际接收的数据长度,出错时返回-1。

2)、服务器使用其接收客户请求,客户使用它接受服务器的应答。如果没有数据,将阻塞,如果收到的数据大于缓存的大小,多余的数据将丢弃。也可以使用recv接收面向连接的UDP的报文

参数说明:

Sockfd:套接字描述符

Buf:指向内存块的指针

Buf_len:内存块大小,以字节为单位

flags:一般为0

 

9、recvfrom()函数

int recvfrom(int sockfd, void *buf, int buf_len,unsigned intflags,struct sockaddr *from,int fromlen);

功能:从UDP接收数据,返回实际接收的字节数,失败时返回-1

参数说明:

Sockfd:套接字描述符

buf:指向内存块的指针

buf_len:内存块大小,以字节为单位

flags:一般为0

 

三、其他函数说明

 

1、IP地址转换函数

inet_aton,inet_addr和inet_ntoa函数

#include <arpa/inet.h>

int inet_aton(const char *strptr,struct in_addr *addrptr);

in_addr_t inet_addr(const char *strptr);

char *inet_ntoa(struct in_addr inaddr);

inet_aton函数将strptr所指向的字符串转换成32位的网络字节序二进制值,并存储在指针addrptr指向的in_addr结构体中,若成功,返回1。

 

2、字节排序函数

系统提供4个函数来进行字节顺序转换:

#include “netinet/in.h”

unsigned short int htons(unsigned short int hostshort);

unsigned long int htonl(unsigned long int hostlong);

unsigned short int ntons(unsigned short int netshort);

unsigned long int ntonl(unsigned long int netlong);

其中。前两个函数将主机字节顺序转换成网络字节顺序;后两个函数将网络字节顺序转换成主机字节顺序。

在使用这些函数时,我们不关心主机或网络顺序的真实值到底是大端还是小端,只需要调用适当的函数来对给定值(函数的整型参数)进行主机字节顺序和网络字节顺序的转换,它们的返回值就是经过转换以后的结果。

 

3、fork()函数

服务器端的进程需要对客户来的请求随时进行响应,因此需要服务器创建新进程的一些函数。

#include <unistd.h>

int fork(void);

fork()函数最特殊的地方就是调用一次返回两次:父进程的返回值是所创建的子进程的ID值;子进程的返回值是0。所以可以从返回值来判断这两次返回是在哪个进程完成的。若返回-1,则调用出错。

fork()函数一般有两个用途:

l  父进程可以通过调用fork()函数来建立自己的拷贝子进程,从而在一个子进程执行任务时另外的子进程同时执行其他的操作。如果进程需要执行其他程序,则应该首先用fork()生成一个拷贝,然后这个拷贝子进程调用exec()函数来执行应用程序。

l  父进程调用fork()前打开的所有套接字描述符都与子进程共享。一般服务器调用accept()后调用fork(),然后子进程继承了accept()的已连接套接字描述符并与之进行通信,而父进程则可以关闭这个套接字。

 

4、close()函数

用来关闭一个套接字描述符。当完成了套接字的建立、绑定、连接和通信读写之后,都需要最后关闭建立的套接字。

 #include <unistd.h>

 int close(int sockfd);

Sockfd是需要关闭的套接字描述符。若执行成功则返回0,否则返回-1。

清除套接字描述符的具体操作:将这个套接字描述符标记为CLOSED状态,然后立即返回进程。TCP协议将继续使用这个套接字,将尚未发送的数据传递到对方,然后发送FIN数据段,关闭和释放这个套接字。这是套接字才被完全删除。

 

5、shutdown()函数

shutdown()函数

可以部分关闭socket连接,单方面的中断连接,即禁止某个方向的信息传递。

 #include <unistd.h>

 int shutdown(int sockfd,inthow);

 sockfd代表需要关闭的套接字描述符;how的值:

0:禁止接收信息

1: 禁止发送信息

2:接收和发送都被禁止,与close()函数效果相同。

 

注:

close()和shutdown()的区别:

close()函数调用时将访问计数减1,只有当访问计数减少到0以后,才关闭套接字;而shutdown()函数可直接发送FIN数据段以终止网络连接,而不管访问计数的多少。

shutdown()实际上只关闭了TCP连接的一半。因为TCP连接是双向的,即包括读和写两个方向。Shutdown()函数可以给服务器发送一个FIN,告诉它已经完成了数据发送,但仍为读而开放套接字描述符。Close()函数则是同时关闭了读写两个方向。


6、sleep()函数

功 能: 执行挂起一段时间

#include <unistd.h>

unsigned sleep (unsigned int seconds);   //秒

int usleep (useconds_t usec);      //微秒

             

四、基于流套接字的Linux Socket程序示例

 

1、服务器端:

#include <unistd.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h> 

int main()
{
	
	int serverStream=-1; //定义套接口描述字
	int clientStream=-1;
	int clientlen=0;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	//创建TCP/IP套接字、流套接字
	char data[]="-----!Welcome to Socket Programming World !";
	serverStream = socket (AF_INET,SOCK_STREAM,0);
	if(serverStream == -1)
	{
		perror(" creating sockrt failed! \n");
		exit(1);
	}
	
	//设置服务器连接地址和端口,对于IPv4
	server_addr.sin_family = AF_INET; //TCP地址结构
	server_addr.sin_port = htons(9736); //绑定到9736端口,并将端口转换成网络数据类型
	server_addr.sin_addr.s_addr = htonl (INADDR_ANY);//不指定IP地址
	//指定IP地址为127.0.0.1;inet_addr将点分十进制转换成32位数字网络字节顺序
	//server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	//bind绑定套接字。成功返回0,失败返回-1
	bind(serverStream, (struct sockaddr*)&server_addr, sizeof(server_addr)); 
	//监听套接字。成功返回0,失败返回-1
	listen(serverStream,5);
	//忽略子进程停止或退出信号
	signal(SIGCHLD, SIG_IGN);

	//等待客户连接请求
	while(1)
	{
		clientlen=sizeof(client_addr);
		printf("\nServer waiting\n");
		//accept()接受连接请求
		clientStream=accept(serverStream,(struct sockaddr*)&client_addr, &clientlen);

		if(fork()==0 ) //子进程返回成功
		{
			printf("\nThe Old information: %s\n",data);
			read(clientStream,&data,5);
			sleep(5);
			write(clientStream,&data, sizeof(data));
			close(clientStream);
			exit(0);
		}
		else
		{
			//父进程中关闭套接字
			close(clientStream);
		}
	}
}


2、客户端:

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

int main()
{
	int stream=-1;
	int result=-1;
	struct sockaddr_in address;
	char data[80]="Hello!!";
	//创建流套接字
	stream = socket(AF_INET,SOCK_STREAM,0);
	//设置服务器连接信息
	address.sin_family = AF_INET; //TCP地址结构
	address.sin_port = htons(9736); //绑定到9736端口,并将端口转换成网络数据类型
	address.sin_addr.s_addr = inet_addr("127.0.0.1");

	result=connect(stream,(struct sockaddr *)&address, sizeof(address));
	if(result==-1)
	{
		perror("connecting failed!\n");
		exit(1);
	}
	//发送数据到服务器
	write(stream,data,5);
	//接收服务器信息
	read(stream,data,50);
	printf("\nThe New information: %s\n",data);
	close(stream);
	exit(0);
}

运行结果:

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- awee.cn 版权所有 湘ICP备2023022495号-5

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务