linux网络编程基础之套接字socket网络通信 附代码详解

socket套接字网络通信代码在最下面,可自取

三种套接字类型

套接字有三种类型:
流式套接字(SOCK_STREAM)
数据报套接字(SOCK_DGRAM)
原始套接字
个人理解:流式套接字采用TCP连接方式,数据报套接字采用UDP连接方式

套接字的一些基本数据结构

struct sockaddr,该结构用来存储套接字地址
数据定义如下:

struct sockaddr{
	unsigned short sa_family;//address族,一般使用AF_INET
	char sa_data[14];//包含一些远程电脑的地址、端口和套接字的数目
}

②但是,该结构在套接字中一般用struct sockaddr_in结构来替代struct sockaddr,该结构定义如下:

sruct sockaddr_in{//后缀in代表Internet
	short int sin_family;//address族,一般使用AF_INET
	unsigned short int sin_port;//端口号
	struct in_addr sin_addr;//Internet地址
	unsigned char sin_zero[8];//添0,目的是和struct sockaddr的大小保持一致
}

struct in_addr用于因特网地址,结构定义如下:

struct in_addr{
	unsigned long s_addr;
}

网络转换函数

网络字节顺序转换函数
由于不同主机的存储有大端字节和小端字节的区别,而在网络传输需要进行统一顺序,这就要用到了网络转换函数。
注意:把数据发送到Internet之前,一定要把它的字节顺序从主机字节顺序转换到网络字节顺序。

htons() -- Host to NetWork Short 主机字节顺序转换为网络字节顺序
htonl() -- Host to NetWork Long  主机字节顺序转换为网络字节顺序
ntohs() -- NetWork to Host Short 网络字节顺序转换为主机字节顺序
ntohl() -- NetWork to Host Long  网络字节顺序转换为主机字节顺序

inet_addr(ip),把ip转换为网络字节,该函数得到的返回值已经是网络字节顺序了,不用再使用htons()函数进行转换了。

struct sockaddr_in my_addr;
my_addr.sin_addr.s_addr = inet_addr("192.111.69.52");

inet_ntoa()用于将in_addr类型的数据转换为一个字符串指针,并返回;

char *p;
p = inet_ntoa(my_addr.sin_addr);
printf("address: %s",p);//以 数字.数字.数字.数字 形式显示出来

基本套接字系统调用

以下系统调用需要经常用到:

c/s通信流程

  • socket()
  • bind()
  • connect()
  • listen()
  • accpet()
  • send()
  • recv()
  • sendto()
  • recvfrom()
  • close()
  • shutdown()

以下是部分常用的系统调用具体说明,代码有详细说明:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);

socket()系统调用用于取得套接字描述符;
其中domain一般设置为"AF_INET";
type则对应套接字的类型,"SOCK_STREAM""SOCK_DGRAM";
protocol只需要设置为0;
该函数返回一个你以后可以使用的套接字描述符
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,struct sockaddr* my_addr, int addrlen);

该函数用于将socket套接字描述符绑定一个机器上的端口;
sockfd为socket()函数返回的套接字描述符
my_addr是一个指向struct sockaddr的指针,一般可以将sockaddr_in对象的地址利用强制类型转换放在此处当做参数,如((struct sockaddr*)&my_addr);
addrlen可以设置为sizeof(struct sockaddr);

注意:使用bind绑定端口号时候,最好将端口参数设置大一点,小于1024的端口号都是被保留下来作为系统使用端口的,你可以使用1024以上的任何端口,一直到65535;
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,struct sockaddr *serv_addr,int addrlen)

sockfd和addrlen的取值和bind()中类似
serv_addr应该是一个存储远程计算机的IP地址和端口信息的结构;

最好对返回值进行检查,如果返回错误值-1表示发生了错误(无法连接到远程主机等);
#include <sys/socket.h>
int listen(int sockfd,int backlog);

listen()是等待别人连接,进行系统侦听请求的函数
sockfd是套接字描述符;
backlog是本地能够等待的未连接的最大数目,推荐使用SOMAXCONN宏;

执行完listen之后,将socket从主动套接字变为被动套接字
#include <sys/socket.h>
int accept(int sockfd, void* addr, int* addrlen);

当有客户从别的地方尝试使用connect()函数来连接某个已经在listen()的端口时候,accept()将返回一个新的套接字描述符,
代表这个连接。而最开始listen()的那个套接字描述符依旧在原来的端口上listen();
sockfd是正在listen()的套接字描述符;
addr指向着从远程连接过来的计算机的信息,是一个struct sockaddr_in结构的指针;
addrlen是本地的一个整型数值,其大小应该是sizeof(struct sockaddr_in);

从已连接队列返回第一个连接,如果已连接队列为空则阻塞
send()recv()是基本的SOCK_STREAM套接字流进行通讯的函数
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd, const void* msg, int len, int flags);

sockfd表示已经建立连接的套接字描述符,如accept()返回的套接字描述符;
msg为发送信息的地址;
len是发送信息的长度;
flags为发送标记,一般设0;
send()函数的返回值为真正发送信息的长度;

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

sockfd表示已经建立连接的套接字描述符,如accept()返回的套接字描述符;
buf是一个指针,指向能够存储数据的缓冲区;
len是缓存区最大尺寸;
flags为接受标记,一般设0;
recv()函数的返回值为真正接受信息的长度;
sendto()recvfrom()函数是无连接UDP通讯时候使用的,此处不做详细介绍;
close(int sockfd);
使用标准的关闭文件的函数来对套接字描述符进行关闭操作;
执行close()函数之后,套接字将不会允许进行读写操作;

int shutdown(int sockfd, int how)
如果想对套接字的关闭进行进一步操作的话,可以使用shutdown()函数;
sockfd为套接字描述符;
how为控制方式变量:0不允许数据接受操作,1不允许数据发送操作,2不允许发送接受操作

在面向连接的协议程序中,服务器端按照以下流程执行

  • 调用socket()创建一个套接字
  • 调用bind()把自己绑定在一个地址上
  • 调用listen()函数侦听连接
  • 调用accept()函数接受所有引入的请求
  • 连接成功之后调用send()和recv()进行和客户机client的通信

在面向连接的协议程序中,客户端按照以下流程执行

  • 调用socket()创建一个套接字
  • 调用bind()把自己绑定在一个地址上
  • 调用connect()函数进行连接
  • 连接成功之后调用send()和recv()进行和服务器server的通信

以下是一个关于cs模型中服务器端和客户端的通信的代码详解:
客户端server.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MYPORT 4000
#define BACKLOG 10
#define MAXDATASIZE 100
int main()
{
	char buf[MAXDATASIZE];
	
	int sock_fd,sock_new;
	//定义套接字描述符
	
	//定义套接字地址对象
	struct sockaddr_in my_addr;
	struct sockaddr_in their_addr;
	
	//初始化本地连接地址
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(MYPORT);
	my_addr.sin_addr.s_addr = INADDR_ANY;
	bzero(&(my_addr.sin_zero),8);
	
	//套接字描述符初始化
	if((sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1)
	{
		perror("socket error!");
		exit(1);
	}
	
	//绑定套接字描述符
	if((bind(sock_fd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))) == -1)
	{
		perror("bind error!");
		exit(1);
	}
	
	//listen()函数监听
	if((listen(sock_fd,BACKLOG)) == -1)
	{
		perror("listen error!");
		exit(1);
	}
	
	//accept()主循环
	//进行listen()之后的套接字进行accept(),只有accept()之后才能进行收发数据
	while(1)
	{
		//sin_size用于accept()中最后一个参数
		int sin_size = sizeof(struct sockaddr_in);
		if((sock_new = accept(sock_fd,(struct sockaddr*)&their_addr,&sin_size)) == -1)
		{
			perror("accpet error!");
			continue;
		}
		printf("server:got connetion form %s\n",inet_ntoa(their_addr.sin_addr));
		
		if(fork() == 0)//fork==0表示此处是子线程里面,子线程用于处理远处的连接
		{
			//发送数据
			if((send(sock_new,"Hello!\n",7,0)) == -1)
			{
				perror("send error!\n");
				close(sock_new);
				exit(0);
			}
			//接受数据
			int len;
			int i = 0;
			//char str[] = "------------------------------->>>>>\n";
			printf("等待第%d次消息接受>>>\n",i+1);
			while(1)
			{
				i++;
				if((len = recv(sock_new,buf,MAXDATASIZE,0)) == -1)
				{
					perror("recv error!");
					exit(0);
				}
				//接受数据打印
				printf("第%d次收到消息,内容如下:\n",i);
				buf[len] = '\0';
				printf("%s",buf);
				bzero(buf,MAXDATASIZE);
				printf("等待第%d次消息接受>>>\n",i+1);
			}
			close(sock_new);
		}
	}
	while(waitpid(-1,NULL,WNOHANG) > 0);
	exit(0);
}

客户端client.c

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

#define MYPORT 4000//端口号码
#define MAXDATASIZE 100//最大缓存空间

int main(int argc, char * argv[])
{
	int sock_fd,numbytes;
	char buf[MAXDATASIZE];
		
	struct hostent* he;
	struct sockaddr_in their_addr;
	
	if(argc != 2)
	{
		fprintf(stderr,"usage:client hostname\n");
		exit(1);
	}
	if((he = gethostbyname(argv[1])) == NULL)
	{
		herror("gethostbyname");
		exit(1);
	}
	
	//使用socket()函数获取可用的套接字
	if((sock_fd = socket(AF_INET,SOCK_STREAM,0) == -1))
	{
		perror("socket error!");
		exit(1);	
	}
	
	//对their_addr进行参数设置
	their_addr.sin_family = AF_INET;
	their_addr.sin_port = htons(MYPORT);
	//这个地方在琢磨一下!!
	their_addr.sin_addr = *((struct in_addr*)he->h_addr);
	bzero(&(their_addr.sin_zero),8);//置零
	
	//connect()函数用于连接设定好的their_addr地址
	if((connect(sock_fd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr))) == -1)
	{
		perror("connect error!");
		exit(1);
	}

	//利用recv()函数接受数据
	if((numbytes = recv(sock_fd,buf,MAXDATASIZE,0)) == -1)
	{
		perror("recv error!");
		exit(1);
	}
	buf[numbytes] = '\0';
	printf("Recvived:%s\n",buf);
	int i = 0;
	//利用send()发送数据
	while(1)
	{	
		i++;
		printf("第%d次输入数据:\n",i);
		//fflush(stdout);
		scanf("%s",buf);
		if(send(sock_fd,buf,sizeof(buf),0) == -1)
		{
			perror("send error!");
			exit(1);
		}
		bzero(buf,MAXDATASIZE);
		printf("第%d次成功发送\n",i);
		fflush(stdout);
	}

	//关闭打开的socket套接字
	close(sock_fd);
	return 0;
}

具体使用方法:

  • 首先用gcc对server.c编译,然后启动server程序
  • 另外打开一个终端,用gcc对client.c编译,然后在命令行中输入以下命令 telnet localhost 4000 ,连接之后在client终端中输入消息,即可在server中得到输出。得到以下输出结果
t@t:~/Desktop/c_s/client$ telnet localhost 4000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello!
hello world!
you are the best!
t@t:~/Desktop/c_s/server$ ./server 
server:got connetion form 127.0.0.1
等待第1次消息接受>>>
第1次收到消息,内容如下:
hello world!
等待第2次消息接受>>>
第2次收到消息,内容如下:
you are the best!
等待第3次消息接受>>>

如果看完了并觉得有收获的话,走过路过留个赞,磕头谢!!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值