文档章节

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

吃一堑消化不良
 吃一堑消化不良
发布于 2016/10/07 19:40
字数 2163
阅读 173
收藏 2
点赞 0
评论 0

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;
}

 

© 著作权归作者所有

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

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

IamOkay ⋅ 2016/04/10 ⋅ 0

iOS的socket开发基础

由于博客迁移至www.coderyi.com,文章请看http://www.coderyi.com/archives/429 socket简介 首先让我们通过一张图知道socket在哪里? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是...

flyicarus ⋅ 2014/11/03 ⋅ 28

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 ⋅ 1

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

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

SibylY ⋅ 2013/08/26 ⋅ 0

tcp多客户端程序设计

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

召唤兽 ⋅ 2014/09/04 ⋅ 6

socket 套接字

 套接字,简单的说就是通信双方的一种约定,用套接字中的相关函数来完成通信过程。应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接...

哈全文 ⋅ 2013/06/26 ⋅ 1

Python实现基于TCP UDP协议的IPv4 IPv6模式客户端和服务端功能示例

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

萌萌小白 ⋅ 04/27 ⋅ 0

C/S架构与多进程多线程

C/S架构与多进程多线程 如题,C/S架构指的是服务器(Server)与客户机(Client)协作完成网络程序功能的一种模式。它是已经在计算机世界活跃数十年的一种古老的软件架构。如今已渗入到各领域IT系...

cnyinlinux ⋅ 2015/04/30 ⋅ 0

基于Socket的UDP和TCP编程介绍

一、概述 TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议。 TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的...

扶殊88 ⋅ 2011/12/08 ⋅ 0

基于Socket的UDP和TCP编程介绍

一、概述 TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议。 TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的...

慎思 ⋅ 2012/08/25 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

6. Shell 函数 和 定向输出

Shell 常用函数 简洁:目前没怎么在Shell 脚本中使用过函数,哈哈,不过,以后可能会用。就像java8的函数式编程,以后获取会用吧,行吧,那咱们简单的看一下具体的使用 Shell函数格式 linux ...

AHUSKY ⋅ 3分钟前 ⋅ 0

MySQL 内核深度优化

MYSQL数据库适用场景广泛,相较于Oracle、DB2性价比更高,Web网站、日志系统、数据仓库等场景都有MYSQL用武之地,但是也存在对于事务性支持不太好(MySQL 5.5版本开始默认引擎才是InnoDB事务...

OSC_cnhwTY ⋅ 10分钟前 ⋅ 0

单片机软件定时器

之前写了一个软件定时器,发现不够优化,和友好,现在重写了 soft_timer.h #ifndef _SOFT_TIMER_H_#define _SOFT_TIMER_H_#include "sys.h"typedef void (*timer_callback_function)(vo...

猎人嘻嘻哈哈的 ⋅ 12分钟前 ⋅ 0

好的资料搜说引擎

鸠摩搜书 简介:鸠摩搜书是一个电子书搜索引擎。它汇集了多个网盘和电子书平台的资源,真所谓大而全。而且它还支持筛选txt,pdf,mobi,epub、azw3格式文件。还显示来自不同网站的资源。对了,...

乔三爷 ⋅ 20分钟前 ⋅ 0

Debian下安装PostgreSQL的表分区插件pg_pathman

先安装基础的编译环境 apt-get install build-essential libssl1.0-dev libkrb5-dev 将pg的bin目录加入环境变量,主要是要使用 pg_config export PATH=$PATH:/usr/lib/postgresql/10/bin 进......

玛雅牛 ⋅ 21分钟前 ⋅ 0

inno安装

#define MyAppName "HoldChipEngin" #define MyAppVersion "1.0" #define MyAppPublisher "Hold Chip, Inc." #define MyAppURL "http://www.holdchip.com/" #define MyAppExeName "HoldChipE......

backtrackx ⋅ 50分钟前 ⋅ 0

Linux(CentOS)下配置php运行环境及nginx解析php

【part1:搭建php环境】 1.选在自己需要安装的安装包版本,wget命令下载到服务器响应目录 http://php.net/releases/ 2.解压安装包 tar zxf php-x.x.x 3.cd到解压目录执行如下操作 cd ../php-...

硅谷课堂 ⋅ 56分钟前 ⋅ 0

Nginx服务架构初探(四):nginx服务器的rewrite功能

nginx服务器的rewrite功能 1.nginx后端服务器组的配置 1>upstream name {…} name是给服务器组限的组名 2>server address [parameters]; address为服务器地址 parame......

余温灬未存 ⋅ 今天 ⋅ 0

layer.prompt使文本框为空的情况下也能点击确定

最近一直在使用layui,但是用到弹出层layer.prompt时,如果文本框是空的话点击确定没有反应,不能向下执行。 但是我又需要空值,看看我原来的代码。 123456789 layer.prompt...

孟飞阳 ⋅ 今天 ⋅ 0

Linux普通文件压缩工具gzip、Bzip2、xz

第六章 文件压缩和打包 6.1 压缩打包介绍 Linux环境常见压缩文件类型: .zip,.gz,.bz2,.xz, .tar.gz,.tar.bz2,.tar.xz 压缩打包的目的 方便文件传输 节省磁盘空间 减少传输花费的时间 ...

弓正 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部