文档章节

TCP 服务器端和客户端程序设计

吃一堑消化不良
 吃一堑消化不良
发布于 2016/10/07 19:40
字数 2163
阅读 178
收藏 2

1设计思路

(1) socket函数:

为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。

int socket(int family,int type,int protocol);  //失败返回-1,并设置errno为相应的值,其它值为成功 

第一个参数指明网络层协议簇,目前支持5种,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);

第二个参数指明套接口类型,有四种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)、SOCK_SEQPACKET(有序分组)和SOCK_RAW(原始套接口)。SOCK_STREAM基于TCP,是有保障的、面向连接的SOCKET,多用于资料(如文件)传送;SOCK_DGRAM基于UDP,是无保障的面向消息的socket , 主要用于在网络上发广播信息。

第三个参数指明相应的传输协议,常见的协议有TCP、UDP、SCTP,要指定它们分别使用宏IPPROTO_TCP、IPPROTO_UPD、IPPROTO_SCTP来指定,如果该值为0则使用默认值。

(2) 套接口地址结构:

这些地址结构的名字均已“sockaddr_”开头,并以对应每个协议族的唯一后缀结束。以IPv4套接口地址结构为例,它以“sockaddr_in”命名,以下是结构体的内容:

struct in_addr {
    in_addr_t s_addr;     /* IPv4地址 */
}; 

struct sockaddr_in {
    uint8_t sin_len;        /* 无符号的8位整数 */
    sa_family_t sin_family; /* 套接口地址结构的地址簇,这里为AF_INET */
    in_port_t sin_port;     /* TCP或UDP端口 */
    struct in_addr sin_addr;
    char sin_zero[8];  
};

(3) setsockopt和getsockopt函数:

(1) setsockopt()函数,用于任意类型、任意状态套接口的设置选项值。

int setsockopt(int scokfd,int level,int name,char *value,int *optlen);

(2) getsockopt()用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval。

int getsockopt(int sockfd,int level,int name,char *value,int optlen);

这两个函数参数相同如下:

第一个参数sockfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对于客户端是调用socket()函数返回的套接字描述符

第二个参数level是选项定义的层次,支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6

第三个参数optname是需设置的选项

第四个参数optval是指针,指向存放选项待设置的新值的缓冲区

第五个参数optlen是optval缓冲区长度

( 4 ) bind函数:

为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。

int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen); //返回0表示成功,-1表示失败 

第一个参数是socket函数返回的套接口描述字;

第二个和第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。

(5) listen函数:

listen函数仅被TCP服务器调用,它的作用是将用sock创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求。

int listen(int sockfd,int backlog); //返回0表示成功,返回-1表示失败

第一个参数是socket函数返回的套接口描述字;

第二个参数规定了内核为此套接口排队的最大连接个数。由于listen函数第二个参数的原因,内核要维护两个队列:已完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三路握手未完成的连接,accept函数是从已完成队列中取连接返回给进程;当已完成队列为空时,进程将进入睡眠状态。

( 6 ) accept函数:

accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态。

int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen); //返回值非负表示成功,-1表示失败

第一个参数是socket函数返回的套接口描述字;第二个和第三个参数分别是一个指向连接方的套接口地址结构和该地址结构的长度;该函数返回的是一个全新的套接口描述字;如果对客户段的信息不感兴趣,可以将第二和第三个参数置为空。

( 7 ) write和read函数:

当服务器和客户端的连接建立起来后,就可以进行数据传输了,服务器和客户端用各自的套接字描述符进行读/写操作。因为套接字描述符也是一种文件描述符,所以可以用文件读/写函数write()和read()进行接收和发送操作。
(1) write()函数用于数据的发送。

int write(int sockfd, char *buf, int len); //返回值非负表示成功,-1表示失败

(2) read()函数用于数据的接收。

int read(int sockfd, char *buf, int len); //返回值非负表示成功,-1表示失败

两个函数参数相同如下:

第一个参数sockfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对于客户端是调用socket()函数返回的套接字描述符;

第二个参数buf是指向一个用于发送 / 接收信息的数据缓冲区;

第三个参数len指明发送 / 接收数据缓冲区的大小。

( 8 ) send和recv函数:

TCP套接字提供了send()和recv()函数,用来发送和接收操作。这两个函数与write()和read()函数很相似,只是多了一个附加的参数。
(1) send()函数用于数据的发送。

ssize_t send(int sockfd, const void *buf, size_t len, int flags); //成功返回写出的字节数,失败返回-1

前3个参数与write()相同,参数flags是传输控制标志。
(2) recv()函数用于数据的发送。

ssize_t recv(int sockfd, void *buf, size_t len, int flags); //成功返回读入的字节数,失败返回-1

前3个参数与read()相同,参数flags是传输控制标志。

( 9 ) close函数:用于关闭socket

2实现代码

TCP服务器端:Windows和Linux皆可

// ServerMain.cpp : Linux/windows Server
// socket()->bind()->listen()->accept()->read()->write()->close()
#include <stdint.h>
#include <stdio.h>

#ifdef _MSC_VER
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#endif

bool CloseSerSocket(uint32_t dwSockId,const char* strError)
{
#ifdef _MSC_VER
	closesocket(dwSockId);
#else
	close(dwSockId);
#endif 
	if (NULL != strError)
	{
		perror(strError);
		return false;
	}
	else
	{
		return true;
	}
}

int main()
{
#ifdef _MSC_VER
	WORD wVersion;
	WSADATA wsData;
	wVersion = MAKEWORD(1, 1);
	if (WSAStartup(wVersion, &wsData) != 0)
	{
		perror("windows network init error");
		return false;
	}

	int nOn = 1000;
#else
	struct timeval nOn = { 1,0 };
#endif 

	// 初始化Socket
	uint32_t dwSocketId = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (dwSocketId == -1)
	{
		return CloseSerSocket(dwSocketId, "init socket error, can not get file description");
	}

	// 设置地址重用
	if (setsockopt(dwSocketId, SOL_SOCKET, SO_REUSEADDR, (const char*)&nOn, sizeof(nOn)) == -1)
	{
		return CloseSerSocket(dwSocketId, "set socket error");
	}

	// 设置服务器信息
	sockaddr_in stServerAddr;
	sockaddr_in stClientAddr;
	stServerAddr.sin_family = AF_INET;
	stServerAddr.sin_port = htons(5050);
	stServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY就是inet_addr("0.0.0.0") 当服务器的监听地址是INADDR_ANY时,就是所有的都监听

	// 绑定socket
	if (bind(dwSocketId,(const sockaddr*)&stServerAddr,sizeof(stServerAddr)) == -1)
	{
		return CloseSerSocket(dwSocketId, "bind socket error");
	}

	// 监听客户端请求
	if (listen(dwSocketId, 100) == -1)
	{
		return CloseSerSocket(dwSocketId, "listen socket error");
	}

	socklen_t nCliSize = 0;
	uint32_t dwSocketFd = 0;
	char reveBuff[2048];
	char sendBuff[2048];
	while (true)
	{
		memset(reveBuff, '\0', sizeof(reveBuff));
		memset(sendBuff, '\0', sizeof(sendBuff));
		nCliSize = sizeof(stClientAddr);

		// 从已完成连接队列头返回一个已完成连接
		if ((dwSocketFd = accept(dwSocketId, (sockaddr*)&stClientAddr, &nCliSize)) == -1)
		{
			return CloseSerSocket(dwSocketId, "accept socket error");
		}

		// 接收客户端包
		if (recv(dwSocketFd, reveBuff, 2048, 0) == -1)
		{
			return CloseSerSocket(dwSocketId, "recv package error");
		}

		sprintf(sendBuff, "i have receive the message:%s from you(%s)", reveBuff, inet_ntoa(stClientAddr.sin_addr));

		// 返回客户包
		if (send(dwSocketFd, sendBuff, strlen(sendBuff), 0) == -1)
		{
			return CloseSerSocket(dwSocketId, "send package error");
		}
	}
	CloseSerSocket(dwSocketId, NULL);
    return 0;
}

 

TCP客户端:仅限Windows端

// ClientMain.cpp : Windows Only
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

bool CloseCliSocket(uint32_t dwSockId, const char* strError)
{
	closesocket(dwSockId);
	if (NULL != strError)
	{
		perror(strError);
		system("pause");
		return false;
	}
	else
	{
		return true;
	}
}

int main()
{
	WORD wVersion;
	WSADATA wsData;
	wVersion = MAKEWORD(1, 1);
	if (WSAStartup(wVersion,&wsData) != 0)
	{
		perror("windows network init error");
		return false;
	}

	// 初始化Socket
	uint32_t dwSocketId = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (dwSocketId == -1)
	{
		return CloseCliSocket(dwSocketId, "init socket error, can not get file description");
	}

	// 设置服务器信息
	sockaddr_in stServerAddr;
	stServerAddr.sin_family = AF_INET;
	stServerAddr.sin_port = htons(5050);
	stServerAddr.sin_addr.s_addr = inet_addr("192.168.1.5"); 

	if (connect(dwSocketId, (const sockaddr*)&stServerAddr, sizeof(stServerAddr)) == -1)
	{
		return CloseCliSocket(dwSocketId, "connect server error");
	}

	char delBuff[2048];
	sprintf(delBuff, "%s", "hello");

	// 发送包
	if (send(dwSocketId, delBuff, strlen(delBuff), 0) == -1)
	{
		return CloseCliSocket(dwSocketId, "send package error");
	}

	memset(delBuff, '\0', sizeof(delBuff));

	// 接收包
	if (recv(dwSocketId, delBuff, 2048, 0) == -1)
	{
		return CloseCliSocket(dwSocketId, "recv package error");
	}

	printf("%s\n", delBuff);
		
	CloseCliSocket(dwSocketId, NULL);
	system("pause");
	return 0;
}

 

© 著作权归作者所有

共有 人打赏支持
吃一堑消化不良
粉丝 28
博文 187
码字总数 112458
作品 0
浦东
程序员
Android NDK Socket(POSIX Socket Api)编程

socket简介 Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。 tcpsocket和udpsocket的具体实现 讲了这么久,终于要开始讲socket的具体实现了,iOS提供了Socket网络编程的接...

IamOkay
2016/04/10
271
0
socket TCP 服务器端程序的问题。。。

#include include pragma comment (lib,"ws2_32.lib") define PORT 8888 define ADDR "127.0.0.1" int main(){WSADATA wsock;SOCKET listensocket,connectsocket;SOCKADDR_IN seraddr,cliadd......

socket
2012/09/20
5.5K
1
TCP(UDP)服务器和客户端程序设计

在HTTP 客户端向服务器发送报文之前,需要用网际协议(Internet Protocol,IP) 地址和端口号在客户端和服务器之间建立一条TCP/IP 连接。 一、实验目的 学习和掌握Linux下的TCP服务器基本原理...

SibylY
2013/08/26
0
0
tcp多客户端程序设计

问题:想设计一个多客户端的tcp程序,客户端每隔1s(或更短)时间向服务器端发送心跳包(几KB),服务器收到后发送响应给客户端,现在设计一个程序开辟多个线程来模拟场景,要求客户端10000...

召唤兽
2014/09/04
766
6
Python实现基于TCP UDP协议的IPv4 IPv6模式客户端和服务端功能示例

本文实例讲述了Python实现基于TCP UDP协议的IPv4 IPv6模式客户端和服务端功能。分享给大家供大家参考,具体如下: 由于目前工作的需要,需要在IPv4和IPv6两种网络模式下TCP和UDP的连接,要做...

萌萌小白
04/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

C++ gflags

gflags是google一个开源的处理命令行参数的库,相比getopt,更加容易使用。 定义参数 gflags主要支持的参数类型包括 DEFINEbool: boolean DEFINEint32: 32-bit integer DEFINEint64: 64-bit ...

SibylY
27分钟前
0
0
intellij IDEA Properties中文unicode转码问题

在IDEA中创建了properties文件,发现默认中文不会自动进行unicode转码。如下 在project settings - File Encoding,在标红的选项上打上勾,确定即可 效果图如下: unicode转码后效果...

muzi1994
28分钟前
0
0
Java IO类库之PipedWriter

一、PipedWriter介绍 PipedWriter是字符管道输出流,继承自Writer,功能与PipedOutputStream类似,通过与PipedReader组合使用实现类似管道的功能,在多线程环境下,一个线程使用PipedWriter...

老韭菜
32分钟前
0
0
精简分页组件(手写)

需要引入CSS(没错就是这4行) .pagelist { text-align: center; color: #666; width: 100%; clear: both; margin: 20px 0; padding-top: 20px }.pagelist a { color: #666; margin: 0 2px;......

AK灬
33分钟前
3
0
29 岁成为阿里巴巴 P8,工作前 5 年完成晋升 3 连跳,他如何做到?

泡泡是我的好朋友。今年 31 岁,毕业后就进了阿里巴巴,工作五年内从 P4 晋升至 P6、P7、P8。 和他很少聊到工作,但总觉得他有很棒的职场心得,应该分享出来,于是有了这次采访。希望对职场新...

Java填坑之路
35分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部