Linux系统编程学习笔记——Socket编程基础

1.Socket简介

前面学习了管道、FIFO、共享内存、消息队列、信号量等这些IPC方法可以在进程之间进行通信,但它们都仅仅限于本机进程之间进行通信。那么不同主机间的进程就可以通过socket使用网络让不同主机上的应用程序进行通信。我们知道互联网是通过TCP/IP协议完成通信。而SOCKET把TCP/IP复杂的协议族集成为相关函数,通过调用socket相关函数就可以完成网络通信。

socket分类

socket分为流socket和数据报socket:

  • 流socket提供了一个可靠的双向的字节流通信信道,即保证发送者传输的数据会完整地到达接收程序,流socket的正常工作需要一对相互连接的socket。

  • 数据报socket允许数据以消息的形式进行交换,在数据报socket中,消息边界得到了保留,但数据传输是不可靠的。消息的到达可能是无序的、重复的或根本无法到达。数据报socket在使用时无需与另一个socket连接。

  • 流socket使用了传输控制协议(TCP),数据报socket使用了用户数据报协议(UDP)。

2.流socket(TCP)

流socket通常分为主动socket和被动socket,被动socket通常也叫服务器,主动socket通常叫服务端。

服务器端socket

  • 通过上图我们可以看到服务器端的流程是:

  1. 调用socket()创建一个新的socket。

  2. 调用bind()将创建的socket绑定在一个地址上,即分配ip和端口。

  3. 调用listen()监听客户端访问。

  4. 调用accept()接收客户端的访问

  5. 调用read()/write()或者send()/recv()传输数据。

  • 注意事项:

①为了便于使用bind()函数,有必要了解一下socket的通用的地址结构struct sockaddr,如下所示。bind()使用时可能有不同结构的socket地址,所以定义了如下的标准地址结构,每种具体的socket地址是在标准地址下的再一次细分,如IPv4 socket地址会被存储在socket_in结构中,如下定义。所以在使用时需要用(struct sockaddr *)转化为标准格式。

struct sockaddr{
	sa_family_t sa_family;		
	char        sa_data[14];	
}

struct sockaddr_in{			//IPv4 socket address
	sa_family_t  sin_family;	//Address family(AF_INET)
	in_port_t    sin_port;		//Port number
	struct in_addr sin_addr;	//IPv4 addreddr{				//IPv4 4-byte address
	in_addr_t s_addr;		//unsigned 32-bit integer
};

②设置端口号,计算机和网络传输时可能大小端模式不一样,网络数据是以大端模式传输的,所以在设置端口号时应该都统一为网络字节序模式,即大端模式。用htons函数进行实现主机和网络字节序转换。

③计算机以二进制来表示端口和IP地址,但是通常我们现实中用点分式十进制表示ip地址,所以可以用inet_aton()和 inet_ntoa()函数进行相互转化。

下面创建了一个服务器函数server.c,自定义了一个端口,服务器通过while(1)一直等待客户端的访问,待有客户端访问时,接收其访问读取其ip地址并打印,并向客户端发送字符串信息,完成后关闭客户端的socket,等待下一个客户端访问。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#define PORT_ID 6666
#define SIZE    100
int main(void)
{
	int sockfd, client_sockfd;
	struct sockaddr_in my_addr, client_addr;
	int addr_len;
	char welcome[SIZE] = "Welcome to connect to the sever!";
	/*
	struct sockaddr{
		sa_family_t sa_family;		
		char        sa_data[14];	
	}
	struct sockaddr_in{			//IPv4 socket address
		sa_family_t  sin_family;	//Address family(AF_INET)
		in_port_t    sin_port;		//Port number
		struct in_addr sin_addr;	//IPv4 address
		unsigned char _pad[x]		//pad to size of 'sockaddr' structure(16 bytes)
	};
	*/
	//初始化服务器的地址
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(PORT_ID);
	my_addr.sin_addr.s_addr = INADDR_ANY;	//可以接收任意客户端访问

	//创建socket
	sockfd = socket(AF_INET, SOCK_STREAM, 0);//create a socket
	//AF_INET:IPv4; SOCK_STREAM:byte stream
	
	//绑定socket
	bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
	
	//监听客户端访问,队列最大为10
	listen(sockfd, 10);
	//10 : listening queue length is 10
	
	addr_len = sizeof(struct sockaddr);
	while(1)
	{
		printf("Server is waiting for client to connect:\n");
		//允许并接收客户端的访问
		client_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_len);
		printf("Client  IP address = %s\n", inet_ntoa(client_addr.sin_addr));
		send(client_sockfd, welcome, SIZE, 0);//发送数据
		printf("Disconnect the client request.\n");
		close(client_sockfd);
	}	
	close(sockfd);
	return 0;
}

客户端socket

客户端的流程是:

  1. 调用socket()创建一个新的socket

  2. 调用connect()将客户端通过地址请求连接到服务器的socket上。

  3. 调用read()/write()或者send()/recv()传输数据。

下面创建了一个客户端的程序client.c,主函数设置输入服务器的ip地址,完成请求通信后,接收服务器发来的字符串并打印。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#define PORT_ID 6666
#define SIZE    100
int main(int argc, char *argv[])
{
	int sockfd, client_sockfd;
	struct sockaddr_in server_addr;
	char buf[SIZE];
	if(argc < 2)
	{
		printf("Usge:./client [server IP address]\n");
		exit(1);
	}
	sockfd = socket(AF_INET, SOCK_STREAM, 0);//create a socket
	//AF_INET:IPv4; SOCK_STREAM:byte stream
	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT_ID);
	server_addr.sin_addr.s_addr =inet_addr((argv[1]));
	connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));

	recv(sockfd, buf, SIZE, 0);
	printf("Client receive from server: %s\n", buf);
	
	close(sockfd);
	return 0;
}

运行结果,由于只有一台电脑,所以服务器和客户端的ip地址是一样的,输入ifconfig,得到ip地址,一个终端运行server.c,此时服务器阻塞,打印"waitting...."后一直等待客户端的访问,另一个终端运行client.c并输入服务器ip地址,即可完成通信,服务器打印客户端的ip并发送字符串,客户端顺利收到并打印。

 3.数据报socket(UDP)

数据报socket的流程如上图所示,相比流socket,数据报socket相对简单,因为它不用实时连接,运行类似于邮政系统。

服务器端socket

服务器端的流程是:

  1. 调用socket()创建一个socket,类似于创建一个邮箱。

  2. 调用bind()绑定服务器的地址(一个众所周知的地址,比如一个公司的邮箱地址,以便允许客户访问)。

  3. 调用recvfrom()/sendto()接收、发送数据报。

  4. 调用close()关闭socket

下面创建了一个服务器函数server.c,自定义了一个端口,服务器通过while(1)一直等待客户端的访问,待有客户端访问时,接收其访问读取其ip地址并接收它发送的数据并打印,然后接收等待下一次访问。


#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#define PORT_ID 6666
#define SIZE    100
int main(void)
{
	int sockfd, client_sockfd;
	struct sockaddr_in my_addr, client_addr;
	int addr_len;
	char buf[SIZE];
	char welcome[SIZE] = "Welcome to connect to the sever!";
	//init my_addr
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(PORT_ID);
	my_addr.sin_addr.s_addr = INADDR_ANY;	//any ip addr 
	//创建socket
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);//create a socket
	//绑定socket
	bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
	addr_len = sizeof(struct sockaddr);
	while(1)
	{
		printf("Server is waiting for client to connect:\n");
		//接收信息
		recvfrom(sockfd, buf, SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
		printf("Server receive ip is %s, receive data is %s\n", inet_ntoa(client_addr.sin_addr) ,buf);
	}
	//关闭socket
	close(sockfd);
	return 0;
}

客户端socket

客户端的流程是:

  1. 调用socket()创建一个socket,类似于创建一个邮箱。

  2. 调用recvfrom()/sendto()接收、发送数据报。

  3. 调用close()关闭socket。

下面创建了一个客户端的程序client.c,主函数设置输入服务器的ip地址,每隔1s发送一次数据,发送五次后关闭socket。


#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#define PORT_ID 6666
#define SIZE    100
int main(int argc, char *argv[])
{
	int sockfd;
	struct sockaddr_in server_addr;
	char buf[SIZE];
	int i;
	if(argc < 2)
	{
		printf("Usge:./client [server IP address]\n");
		exit(1);
	}
	//创建socket
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);//create a socket
	//输入服务器地址
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT_ID);
	server_addr.sin_addr.s_addr =inet_addr((argv[1]));

	for(i = 0; i < 5; i++)
	{
		sprintf(buf, "%d\n", i);
		//发送数据
		sendto(sockfd, buf, SIZE, 0, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
		printf("Client sends to server %s: %s\n", argv[1], buf);
		sleep(1);
	}
	//关闭socket
	close(sockfd);
	return 0;
}
  • 运行结果:由于只有一台电脑,所以服务器和客户端的ip地址是一样的,输入ifconfig,得到ip地址,一个终端运行server.c,此时服务器阻塞,打印"waitting...."后一直等待客户端的访问,另一个终端运行client.c并输入服务器ip地址,并向服务器发送数据,服务器接收访问打印客户端的ip和接收到的数据。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值