Linux网络socket编程

本文介绍Linux环境下TCP Socket编程的基础知识,包括Socket类型、数据传输、名字地址转换等内容,并提供了具体的编程实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

socket网络编程时linux中数据传输的一种重要的方式。今天所介绍的是tcp的编程。

一、socket有三种类型:

1、流式socket(SOCK_STREAM)

流式套接字提供可靠、面向连接的通信流;它使用tcp协议,从而保证了数据传输的正确性和顺序性。说白了就是这家伙可靠。

2、数据包socket(SOCKET_DGRAM)

数据包套接字是一种无连接的服务,数据通过相互独立的报文进行传输,它是无序的。容易出错,使用的是UDP协议。

3、原始socket

原始套接字允许对底层协议如IP和ICMP进行直接访问,功能强大但使用不便。

常用的两个数据类型:sockaddr和sockaddr_in。

struct sockaddr {
   unsigned short sa_family;     /* address family, AF_xxx */
   char sa_data[14];                 /* 14 bytes of protocol address */
  };
说明:
sa_family :是2字节的地址家族,一般都是“AF_xxx”的形式。通常大多用的是都是AF_INET。
  sa_data : 是14字节的协议地址。

struct sockaddr_in {
  short int sin_family;                      /* Address family */
  unsigned short int sin_port;       /* Port number */
  struct in_addr sin_addr;              /* Internet address */
  unsigned char sin_zero[8];         /* Same size as struct sockaddr */
  };
  sin_family:指代协议族,在socket编程中只能是AF_INET
  sin_port:存储端口号(使用网络字节顺序)
  sin_addr:存储IP地址,使用in_addr这个数据结构
  sin_zero:是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。

struct in_addr{
unsigned long s_addr;
};

二、数据传输的优先顺序:
计算机存储有两种方式:大端存储和小端存储。在网络中的传输是大端模式,因此需要进行转换。这里用到了四个函数:htons、ntohs、htonl、ntohl。分别实现网络字节序和主机字节序的转换。

三、名字地址转换
gethostname和gethostaddr都会涉及hostent的结构体。



四、socket基础编程
好了,以上的介绍就相当于socket编程做了一点小小的普及,只是我们在socket编程中用到的工具而已,不用死死的揪住不放。下面才是socket编程的重点的地方。工欲善其事,必先利其器。前面我们把我们的武器拿出了一部分,只是武器而已。
下面进行socket编程的步骤,在编程的过程中跟着步骤走就是了,就如同学习武术一样,一招一式跟着学就是了。
1、socket:该函数用于建立一个socket连接,可指定socket类型的信息。其实socket就是一个文件,建立socket之后,就可以对socketaddr或者socketaddr_in进行初始化,保存socket的信息。
2、bind:该函数用于将ip地址绑定端口号,若绑定其他地址则不成功。另外它主要用于TCP的连接,而UDP的客户端并不需要绑定。其实原理很简单,TCP的传输类似于手机的通话,是一对一的;而UDP的通话类似于短信的形式,可以有很多人对同一个手机发送短信。所以在UDP客户端中就不需要将ip地址绑定为端口号。
3、connect:此函数用于客户端,主要是建立和服务器的连接。connect在UDP编程中有点类似于bind函数的作用。
4、send和recv:这两个函数用于接收和发送数据。可以用在TCP和UDP中。当用在UDP中,客户端可以在connect函数之后使用。
5、sendto和recvfrom:这两个函数和send和recv函数功能是类似的。当用在TCP协议中,后面和地址有关的参数不起作用,函数等同于send和recv。在UDP中,可以用在之前没有用connect函数情况中,这两个函数可以自动寻找制定的地址并进行连接。

下图是TCP的模型:

下图是UDP模型:


接下来就是几个函数的模型,不用记,在使用的过程中在查询就是了。




五、TCP编程实例:

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define SERVPORT 4444
#define BACKLOG 10
#define MAX_CONNECTED_NO 10
#define MAXDATASIZE 100

int main()
{
	struct sockaddr_in server_sockaddr,client_sockaddr;
	int sin_size,recvbytes;
	int sockfd,client_fd;
	char buf[MAXDATASIZE];
	if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){
		perror("socket");
		exit(1);
	}
	printf("socket success!,sockfd=%d\n",sockfd);
	server_sockaddr.sin_family=AF_INET;
	server_sockaddr.sin_port=htons(SERVPORT);
	server_sockaddr.sin_addr.s_addr=INADDR_ANY;
	bzero(&(server_sockaddr.sin_zero),8);
	if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){
		perror("bind");
		exit(1);
	}
	printf("bind success!\n");
	if(listen(sockfd,BACKLOG)==-1){
		perror("listen");
		exit(1);
	}
	printf("listening....\n");
	sin_size=sizeof(struct sockaddr_in); 
		printf("sin_size = %d\n",sin_size);
	if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))==-1){
		perror("accept");
		exit(1);
	}
	if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){
		perror("recv");
		exit(1);
	}
 	printf("received a connection :%s\n",buf);
	
	close(sockfd);
}
client.c


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define SERVPORT 4444
#define MAXDATASIZE 100
main(int argc,char *argv[]){
	int sockfd,sendbytes;
	char buf[MAXDATASIZE];
	struct hostent *host;
	struct sockaddr_in serv_addr;
	if(argc < 2){
		fprintf(stderr,"Please enter the server's hostname!\n");
		exit(1);
	}
	if((host=gethostbyname(argv[1]))==NULL){
		perror("gethostbyname");
		exit(1);
	}
	if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
		perror("socket");
		exit(1);
	}
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_port=htons(SERVPORT);
	serv_addr.sin_addr=*((struct in_addr *)host->h_addr);
	bzero(&(serv_addr.sin_zero),8);
	if(connect(sockfd,(struct sockaddr *)&serv_addr,\
		sizeof(struct sockaddr))==-1){
		perror("connect");
		exit(1);
	}
	if((sendbytes=send(sockfd,"hello",6,0))==-1){
		perror("send");
		exit(1);
	}
	
	close(sockfd);
}

可以在主机上运行server.c,把client.c使用交叉环境编译,下载到开发板上运行,通过TCP实现开发板和主机的通讯。

首先在主机上运行server.c,然后在开发板上运行client.c.



在开发板上执行的程序:



注意事项:

一、在客户端执行client时,后面跟随的ip地址是主机的ip地址。

二、在accept函数中,一定要初始化sin_size的大小,否则在运行的结果中会出现:error:Invalid argument

一般初始sin_size采用的方法是:

sin_size=sizeof(struct sockaddr_in); 

三、accept函数:系统调用 accept() 会有点古怪的地方的!你可以想象发生 这样的事情:有人从很远的地方通过一个你在侦听 (listen()) 的端口连接 (connect()) 到你的机器。它的连接将加入到等待接受 (accept()) 的队列 中。你调用 accept() 告诉它你有空闲的连接。它将返回一个新的套接字文 件描述符!这样你就有两个套接字了,原来的一个还在侦听你的那个端口, 新的在准备发送 (send()) 和接收 ( recv()) 数据。

四、定义的tcp的端口号:无论是tcp还是udp,端口号的范围是从1-65535。端口号分为三个类型:

1)公认端口(Well Known Ports):从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。 

(2)注册端口(Registered Ports):从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。 

(3)动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535。理论上,不应为服务分配这些端口。

五、在为TCP分配buff空间的时候,可以尽量的分配的大些,比如1024.这样在接收客户端较多数据时不至于出错。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值